Compare commits

..
Author SHA1 Message Date
Codex df84f8a984 audio: play native multistream XMA2 with borrowed PCM tails
Add bounded XMA2 packet traversal and one-shot overlap draining. Honor speaker masks when folding WMA/XMA to stereo and expose .xma through the gated mixer and native player paths.

Compare startup, sample count and tail PCM to a pinned FATE reference in CI; retain allocation-free decoding and the existing raw-frame contract.
2026-11-08 18:44:02 +01:00
Codex 6e59d31074 audio: stream native WMA through mixer and media player
Add borrowed ASF and RIFF/xWMA streams with bounded fragment assembly. Drain decoder planes directly into audio_transfer output and reuse the mixer's streaming voices. Route music input through VFS-backed moving windows.

The builtin player uses source-rate integer intervals, float callbacks when supported and a legacy S16 fallback. Respect partial callback acceptance and reset without retaining a growing output queue. Gate the feature with HAVE_RWMA, disabled by default, and preserve FFmpeg/MPV selection.

Test analytic PCM, packet fragments and refill, loop reset, implicit xWMA setup, truncated objects, sample-rate cadence, callback backpressure and codec-disabled builds.
2026-11-08 18:32:37 +01:00
LibretroAdmin 0108ade537 input: a pointer read walks a port's users only while the laser points
With OpenXR built, every RETRO_DEVICE_POINTER and LIGHTGUN read walked
the core port's users to gather their slots before asking the headset,
which answers false at once whenever its laser does not drive a core's
pointer - nearly always without a headset. The headset is now asked
first whether it does, and the walk is skipped when not.

openxr_input_test checks the answer with the laser off and on.
2026-11-08 18:29:34 +01:00
Codex 915cd52c19 formats: add native C89 XMA and WMA decoders
Adapt the MIT WaxFlow spectral decoders with immutable codebooks, reusable transform storage and borrowed PCM output. Keep compressed carry only for frames crossing packet boundaries and look up reused envelopes without copying a resampled curve. Preserve the upstream numeric-table provenance and MIT notices.

Add strict C89, direct-transform, malformed-input and packet-continuation regressions, plus an optional test-only FFmpeg PCM oracle. Gate frontend builds with HAVE_RWMA; playback integration and core replacement remain separate changes.
2026-11-08 17:36:03 +01:00
LibretroAdmin 8770667b58 input: the menu's right stick reads the list's type, not a whole entry
To tell a playlist, where the right stick cycles thumbnails, from any
other list, the menu's stick handling built a full menu_entry_t for the
list's first entry - six string buffers, over 4 KiB on the stack, and
every label and value callback run - every frame the right stick was
held, only to read its type. The type is read from the list instead.

input_driver_collect_system_input()'s frame goes from 4304 to 576
bytes, back under the 4 KiB budget.

frontend_overhead_test gains a lane: in a playlist the right stick is
its own bind, elsewhere the D-pad.
2026-11-08 17:18:10 +01:00
LibretroAdmin 188534011c input: the poll makes the Plays As join again only after a change
Every poll made the join - which user's controller plays as which
player - for every user, and compared it with the last one. It is
made from the Plays As entries, the controllers' claims and names,
Max Users and the users' Device Index, none of which a frame changes.

The poll now makes it only when one of them moved since it last did:
input_plays_as_gen, which already moved for most changes to entries,
also moves wherever a claim, an Auto player, a share or a
controller's name is written, and the two settings are compared with
what the poll last saw.

mapped_port_test checks after each poll that the join kept is the one
the poll would make; a poll that never makes it again fails there in
25 places, 52 with OpenXR.

Callgrind, frontend_overhead input lane: the join's 3.7M instructions
a run are gone.
2026-11-08 17:11:45 +01:00
LibretroAdmin 74eaf88787 input: a core port's rumble writes each pad once
With two users on the same pad (Device Index) and the same core port,
the rumble flush wrote that pad's motor once for each user: two driver
writes, an ioctl or a HID transfer each, for one frame's value. A mask
of the pads written skips the repeat.

mapped_port_test covers it.
2026-11-08 17:11:42 +01:00
LibretroAdmin 2f67fbaa0d input: Tap Delay's idle check goes under the core read's one gate
Each core read tested Tap Delay's idle mask on its own before the
existing test for held-back buttons and the enabler's game buttons.
With Tap Delay idle or off that was one more load and branch on every
read. The check now sits inside that gate, which a flag set with the
idle mask opens.

Callgrind, frontend_overhead input lane: the core reads' wrapper back
to master's cost before Plays As; with the two patches after it the
lane goes from 485.7M to 469.5M instructions.
2026-11-08 17:11:38 +01:00
LibretroAdmin 278abcbc51 intl: the first-run download prompt in one string literal
The translation sync failed converting msg_hash_tt.json: "packed
emitter: keys not in source: MSG_FIRST_RUN_DOWNLOAD_PROMPT". The
prompt's English text was three adjacent literals on three lines,
which h2json does not join, so the key never reached msg_hash_us.json
while the translations carry it. It is one literal now; h2json
exports it and json2h converts a translation holding it.
2026-11-08 17:09:07 +01:00
LibretroAdmin a194c14243 d3d9_ex_test: a hardware view's copy leaves the slot's size alone
2c060e0 stopped d3d9_hlsl_views_hw_copy() from storing the view's size
as the slot's last_dims, which kept the first pass from making the
slot's vertices; d3d9_ex_test still asked for the old store and failed
"the slot holds the view's size" on both devices. It now checks the
copy leaves last_dims as it found it.

overlay_behind_menu, read only under HAVE_OVERLAY, is declared there
too: the test's build without it warned it unused.
2026-11-08 17:09:03 +01:00
Codex 925ba3a9d8 Remove LGPL-derived WMA decoders from libretro-common
Revert 7777f39f5c: libretro-common requires an MIT implementation. Remove the derived decoder sources, codebooks and optional build/CI wiring. A replacement must use an independent clean-room process; the LGPL port must not be relabeled or reused as MIT.
2026-11-08 17:03:28 +01:00
Codex 7777f39f5c formats: add self-contained XMA and WMA audio decoders
Adapt LGPL WMA spectral decoding and codebooks behind a C89 packet API without a libavcodec dependency. Decode raw XMA bit spans directly and lend planar float PCM from reusable context storage. Allocate only at creation, retain bounded split-packet history, and keep file I/O, playback and resampling outside the decoder.

Add optional HAVE_RWMA sources, analytical XMA/IMDCT and packet-history tests, malformed-input and allocation checks, and a test-only FFmpeg PCM oracle in CI. Linux ASan/UBSan, MinGW and MSVC focused tests pass. Core migration, history serialization and gameplay validation remain separate.
2026-11-08 16:53:33 +01:00
LibretroAdmin dfbfe56b05 apple/fastlane: ignore altool's resume-state error on successful uploads
altool from Xcode 27.2 logs an ERROR line about
CDUploaderStateInfoResumeFilePath even when the upload succeeds, and
fastlane fails the upload on any ERROR line from altool. Skip that line
so upload_to_testflight continues to processing and tester distribution;
every other altool error still fails the upload.
2026-11-08 16:22:20 +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
LibretroAdmin fbe4d9a180 rzstd: dictionaries, to decode and to encode
Frames compressed with a dictionary were refused. rzstd_dict_new()
takes one as the reference trainer writes it - ID, the literals'
Huffman tree, the three sequence tables and the recent offsets, then
content - or plain content, and rzstd_decode_dict(),
rzstd_decode_reuse_dict() and rzstd_encode_dict() use it. A frame
starts from the dictionary's tables and offsets, and its matches reach
back into the content through the path the block ring already takes
for a window that has come round. The encoder searches an index of the
content built once with the dictionary, starts from its offsets and
names it in the frame header; its blocks write tables of their own.
A dictionary is only read once built, so threads can share one.

The other entry points still refuse a frame that names a dictionary,
now in the frame's begin rather than its header reader, so
rzstd_frame_content_size() reads such a frame's size.

rzstd_dict_test decodes frames the reference compressed with a trained
dictionary, which need its tables, and refuses them without it or with
another; and round-trips the encoder's frames at every level with
either kind of dictionary.
2026-11-08 15:27:13 +01:00
LibretroAdmin 1c306f97ec rdeflate: resume the header and trailer through a small output
A call that ran out of output while the trailer was being written came
back into the block loop on the next call, parsed the finished input
again and emitted another, empty, final block before the trailer: the
stream decoded wrong, and with one byte of output at a time it never
ended. The zlib and gzip headers were only written when the whole
header fit, so an output smaller than the header made no progress at
all. Both now resume where they stopped.

rinflate_window_test compresses through outputs of 1 to 7 bytes in
each container and checks the result is the one-shot stream.
2026-11-08 15:27:13 +01:00
LibretroAdmin 2c060e0973 d3d9_hlsl: a hardware view's copy leaves its slot's vertices to the first pass
Windows views under Wine failed every d3d9_hlsl exact-3ds case run
through a preset on the core's Direct3D 9 texture with a black screen,
since e2c75d8. The view copied into the chain's frame was right, read
back texel for texel, but d3d9_hlsl_views_hw_copy() then stored the
view's size as the slot's last_dims: the first pass took that to mean
the slot's vertex buffer was already made for that size and never
wrote it, so it drew from an empty buffer. The copy no longer stores
it; the first pass makes the vertices when the size differs and the
chain's end records it, as for a software frame.

Under Wine on 26.04 the five hardware preset cases pass, and the whole
d3d9_hlsl lane on Direct3D9Ex and on the legacy device.
2026-11-08 14:50:04 +01:00
LibretroAdmin 2225f17144 Select SDL2 explicitly in the Vulkan validation job
The views cases exercise the SDL2 driver. Keep that driver enabled when SDL3 is also available, and fail configure checks if it is absent.
2026-11-08 03:30:14 +01:00
LibretroAdmin a4fc5fe8b2 glsl_rect: allow linear filtering the step glsl_rect.h allows
On Ubuntu 26.04's llvmpipe (LLVM 21) the blur shader read in place
from a hardware-ordered frame came out 33 to 54 bytes apart from the
copy, each by one step, and glsl_in_place_test's three linear
comparisons did the same: its reads land on texel edges, where
glsl_rect.h already says a GPU compiling the other program may weigh
the texels one step apart. Linear comparisons now pass within one step;
nearest still has to match byte for byte. Both tests pass on 26.04.
2026-11-08 03:18:26 +01:00
LibretroAdmin 4f7ec8f83d samples: no first-run download prompt over the menus the tests drive
Since 5bf0c36 the first-run download prompt is offered when the assets
folder does not exist yet, as in every fresh config these tests write,
and it comes up as a confirm message over the menu, which then holds
the menu's input. stdio_e2e's MENU down left the selection on 0, and
the views e2e's pointer-sbs-full cases on x11 and sdl3 found the menu's
cursor still at the first pointer position. Both configs now set
rgui_show_start_screen false, which the welcome screen and the prompt
both hang off.

On 26.04 stdio_e2e passes and the x11 pointer cases pass, both
pointer-sbs-full cases failing without it as on CI.
2026-11-08 03:13:20 +01:00
LibretroAdmin 5a28129aa9 Prepare HEVC pictures for their presentation interval
Discard pictures that expire before the next presentation deadline, then retain one covering that deadline. Keep speculative durations and ordinals private until presentation, freeze retry choices, and clear pending progress on seek, restore or renderer shutdown.

Account for cached and prefetched leases alongside reordered VA reads within the existing 32-surface bound. Expose a read-only upcoming-frame end on the stream pacing clock, with checked arithmetic and portable timing, catch-up and save-state regressions.
2026-11-08 02:19:21 +01:00
libretroadmin bbf473c604 Pipeline HEVC playback through physically writable surfaces
Let VA report its nonblocking writable mask and spare surface capacity before preparing a decode target. Preserve prepared retries and distinguish physical ownership from DPB pressure.

Opt the MP4 core into consumed-input progress and hold one future HEVC picture until its interval is due. Keep public clocks and WPOS state on the displayed picture, releasing speculative leases on seek, reset and renderer shutdown. Legacy stream callers retain their retry behavior.
2026-11-08 02:19:21 +01:00
libretroadmin b0d276290c Refuse unrepresentable MP4 saves during pending seeks
WPOSv3 cannot encode a pending user seek target. Return false without writing until scanning and decoding finish, while allowing pending state restorations and already decoded targets to serialize normally.
2026-11-08 02:19:21 +01:00
libretroadmin b0f7dba207 Preserve MP4 empty-interval seeks through audio EOF
Keep requested movie positions outside video intervals, clamp audio to its declared presentation edit, and stop emitting after shutdown. Check exact remaining PCM and the final picture on CPU and native hardware paths.
2026-11-08 02:19:21 +01:00
libretroadmin 0ecb233ebf Use movie timestamps for builtin CPU MP4 seeks
Select the presentation slot in movie coordinates, accounting for initial decoder PTS and leading edits. Keep raw decoder seeks unchanged and compare reordered H.264/HEVC core seeks with sequential picture references.
2026-11-08 02:19:21 +01:00
libretroadmin 805f2ca8f0 Show black when hardware MP4 seeks enter an empty edit
Clear the cached movie with an intentional blank frame during a leading video gap, including hardware state restores. Preserve the first decoded picture when the gap expires and keep required hardware decoding separate from blank presentation.
2026-11-08 02:19:21 +01:00
libretroadmin 700dd56548 media: defer MP4 hardware state restores on backpressure
Retain the restored display ordinal and serialized clocks while a hardware decoder catches up. Retry without advancing playback, preserve pending restores across renderer replacement, and cancel them on reset or a newer seek.
2026-11-08 02:19:21 +01:00
libretroadmin decee999c2 hevc: discard unused hardware outputs during seeks
Advance discarded hardware pictures without exporting output leases. Rotate free decode slots so an asynchronous native write does not immediately block reuse of the first free slot. Preserve normal output and busy retry behavior.
2026-11-08 02:19:21 +01:00
libretroadmin 7c4dbc0ccb media: resume MP4 hardware seeks across backpressure
Keep movie-coordinate seeks pending until the requested picture is presented. Preserve the request across decoder and importer backpressure, cancel it on reset or a newer seek, and restart it after renderer replacement.
2026-11-08 02:19:21 +01:00
libretroadmin 0ad8df45f6 media: hold optional hardware decode at startup
Keep the MP4 clock steady while a selected hardware decoder waits for its first picture, including codecs with a software fallback. Cover decoder and output backpressure in the HEVC core ownership test.
2026-11-08 02:19:21 +01:00
libretroadmin cb17d039f1 mp4: retain exported pictures across decoder backpressure
Keep the ready hardware picture available when the following packet cannot decode yet. Its lease remains valid until replacement or transfer to the caller; only decoder-owned planes expire on a decode attempt. Cover a held H.264/HEVC picture followed by decoder and output backpressure, including single transfer and balanced release.
2026-11-08 02:19:21 +01:00
libretroadmin de9cd88e1b tests: signal HEVC fixture color metadata explicitly
Set the x265 VUI values alongside the container color fields so Main and Main10 fixtures retain BT.709 transfer metadata across FFmpeg versions.
2026-11-08 02:19:21 +01:00
Codex 15c9dbac47 vulkan: retire retained texture resources before device reset
Destroy stream and VA import resources that remain attached to frontend-held texture handles. Preserve the handle allocation through immediate or deferred retirement so reset neither leaks device children nor frees a handle still owned by the frontend.
2026-11-08 02:19:21 +01:00
Codex e9078c7e4a vulkan: preserve frame ownership while Wayland is suspended
Retain the submitted image until presentation resumes and advance offscreen frame slots only after their fences complete. Poll busy slots without adding a wait or reusing the submitted fence and binary semaphore.
2026-11-08 02:19:21 +01:00
Codex 19e4179561 tests: reject Vulkan validation errors in media playback 2026-11-08 02:19:21 +01:00
Codex 47cf9486f5 tests: link native VA import oracle with libm 2026-11-08 02:19:21 +01:00
Codex 9009cbf6ce vulkan: allow padded VA DMA-BUF backing allocations
Use the measured DMA-BUF size for import when it covers the VA descriptor and image memory requirements. Intel exports can report less storage than the kernel backing object; continue refusing undersized objects.
2026-11-08 02:19:21 +01:00
Codex 3a599c56a2 vaapi: submit HEVC entry offsets as one buffer
Intel rejects multiple elements outside slice-parameter buffers. Pack the entry offsets into a single subset buffer while preserving the validated coded offsets.
2026-11-08 02:19:21 +01:00
Codex 5f59c1f42b media: add VA-API HEVC Vulkan PRIME playback
Submit bounded HEVC long-slice metadata through an owned VA session and export native surfaces to a matching Vulkan DRM device. Retain producer and renderer leases through asynchronous submission and device teardown, and refuse unsupported import combinations before publication.

Preserve sampled YUV chroma positioning in GPU conversion and cover decoder, import, retirement and threadless contracts.
2026-11-08 02:19:21 +01:00
LibretroAdmin e2c75d8fbd d3d9_hlsl: a hardware core's views through a preset
A core rendering with the Direct3D 9 interface had its texture drawn
whole through a preset, its views ignored: each view's chain only took
a view of a software frame, written straight out of it into the
chain's own texture. Now each view of the core's texture is copied on
the GPU into its chain's frame, with StretchRect, point sampled, from
where it lies to the corner: the texels as they are, one copy a view,
with no trip through the CPU. The chain's frames are then render
targets in the core's texture's format, made with the chain and black
past the view, filled black again when a smaller view lands in a slot
that held a larger one. They stay its history, so a preset reading
earlier frames reads each view's own.

A chain made for one kind of frame is made again for the other. A
frame without a texture handed over draws no views.

samples/gfx/d3d9_ex checks the copy on a real Direct3D 9 device and a
Direct3D9Ex one under Wine, texel for texel and black past the view,
with a smaller view after a larger one. e2e/run.py now runs the d3d9
hardware case through D3D9's own presets too, earlier frames
included, matching the software frame's screen, and checks the log
says the views were copied out of the core's texture.
2026-11-08 02:12:23 +01:00
LibretroAdmin 87c0ebe857 d3d9_hlsl: a hardware core's views drawn straight from its texture
A core rendering with the Direct3D 9 interface (libretro_d3d9.h) had
its views ignored: its texture was drawn whole, every screen of it as
one picture, and the views fallback reported. Without a preset its
views are now drawn straight from the core's texture, where they lie,
as a software frame's are from the texture it is uploaded into: no
copy. The texture is unbound after, as the frame's own draw leaves it,
for the core to draw into next. Through a preset, whose chains each
take a view of a software frame written straight in, the core's
texture is still drawn whole.

The video_views test core gets a d3d9 mode for video_views_test_hw: it
writes its frame into a system memory surface, copies it with
UpdateSurface into a render target texture per sync index (the
regular maximum's size with video_views_test_max large) and hands it
over with set_texture, taking the device only between lock_context and
unlock_context. e2e/run.py runs the 3DS cases on d3d9_hlsl from that
texture - 2D, side by side, anaglyph, interlaced and threaded - and
the pixel-for-pixel 3DS case, which must match the software frame's;
the Windows views workflow under Wine runs them, plain and on a device
without Direct3D9Ex.
2026-11-08 01:58:56 +01:00
LibretroAdmin 7a1e7f8e5e database_scan_test: pass the scan's user data to task_push_dbscan
task_push_dbscan() takes a third argument, user data, and the
cancelled-scan case added with the arcade set fingerprints called it
with two, so the test no longer built.
2026-11-08 01:24:08 +01:00
LibretroAdmin 3174aeef4a slang: presets with frame history read views in place too
A glcore or Vulkan preset reading earlier frames (OriginalHistory1 and
on) kept copying each view of a hardware frame out to a texture of its
own, then copied that texture again into the history at the frame's
end: two copies a view a frame. Its passes can read the view where it
lies like any other preset's; only the history needs a copy, and that
copy can come straight from the view's rectangle. So such a preset now
reads in place, and the history takes the one copy:

- Vulkan: vkCmdCopyImage from the view's origin in the frame, no longer
  from the corner of a copy. Copies of the same texels, the same size.
- glcore: the history's quad copy samples the view's rectangle in the
  frame, as the copy out of a larger texture already did, nearest at
  texel centres.

gl3_framebuffer_copy_partial() made, filled and deleted a vertex buffer
on every call; the chain now makes that buffer once and the copy writes
its quad into it, so neither the history copy nor the copy out of a
larger texture allocates mid-game.

samples/gfx/gl3_shader_status and samples/gfx/vulkan_chain_alloc check
that a chain with history reads in place, that the frame is not copied
out first, and that the history copy comes from the view's rectangle
(and, on glcore, through the chain's own buffer); both fail on the old
code.
2026-11-08 00:42:45 +01:00
LibretroAdmin e9819f388b input: the null driver says no key is down
The null input driver had no keys_down, so the frontend took the slow
fallback for it: every frame it asked for every hotkey and every
user's turbo and hold bind one at a time, since it could not know that
none of them can be pressed. With keys_down saying no key is down, the
hotkey pass and the poll skip those reads, as they do for the drivers
that give their keys.

Callgrind over the frontend harness's input-cost lane, which runs on
this driver: 40% fewer instructions in all, 83% in the hotkey pass and
50% in input_driver_poll. Every lane gives the same results.

The harness mocks that copy the null driver to answer keys through
input_state now clear keys_down, so they still test that kind of
driver.
2026-11-08 00:15:24 +01:00
LibretroAdmin 41c09f6df5 glsl: offset reads, overloads and early helpers read the frame in place
Four kinds of shader kept the copy of a hardware frame's view; each now
reads the frame where it lies.

textureOffset and textureLodOffset: scalefx and cubic read the frame a
whole number of texels away. The helper takes the offset in the view's
texels: nearest adds it to the texel picked before wrapping, as a
sampler does, linear moves the coordinate by it before clamping.
RARCH_frame(c) is RARCH_frame_at(c, vec2(0.0)), so other reads come out
as before; RARCH_FrameTexels.zw carries 1 / the view's size.

Overloaded functions given the frame: crt-royale's tex2D_linearize
comes in float2 and float3 forms. A call goes to one of them, so when
every call gives the frame at a place and every form takes a sampler
there, all of them carry it; one form that can't keeps the copy for
all. Which parameters carry the frame now grows from the calls that
pass it, so functions that only call each other carry nothing.

A function reading the frame ahead of its declaration: crt-royale
declares its input after its helpers. The declaration, when it is the
only one, moves up to just before the first function reading it, and
the helper with it.

An undef of a read's name: crt-royale undefines COMPAT_TEXTURE and
defines it again. Between the two it means nothing, so it is only kept
from the rewrite when code declares a function of that name.

A define's own parameters no longer count as uses of a function's
parameter of the same name, so COMPAT_TEXTURE_LOD(s, c, l) does not
keep a function taking a sampler s from carrying the frame.

glsl-shaders: 661 of 684 shader files rewrite (647 before), and 510 of
621 presets read in place (493 before); 100 of the rest read earlier
frames, which only the copy keeps. Every newly covered shader matches
the copy byte for byte on llvmpipe, desktop GL and GLES 3; nothing that
matched before changed. samples/gfx/glsl_rect adds offset and early-
declaration shaders to the byte-for-byte oracle, and overload, undef
and define cases to the rewrite checks.
2026-11-08 00:14:43 +01:00
LibretroAdmin 5bf0c36d14 First-run downloads: reuse the Online Updater path, fix the space check
Follow-up to the first-run download prompt (PR #18878).

- Downloads go through action_ok_download_generic (new public wrapper
  action_ok_download_bundle) instead of a copied version of it.  The
  copy predated the stream-to-disk sink, so a first run held the 72MB
  assets zip and the 52MB database zip in RAM, and skipped the check
  that refuses to truncate an archive still being extracted.
- The free-space check asked about the target directory, which on a
  real first run does not exist yet: statvfs failed, -1 came back, and
  assets and databases were never offered.  It now asks the nearest
  existing parent, and an unknown answer no longer blocks the download.
- The prompt is an ordinary msg enum through menu_dialog_confirm_set.
  The 512-byte confirm_text buffer in menu_dialog, the second confirm
  setter and the extra branches in the iterate path are gone.
- Missing bundles are scanned once at menu init into a bitmask that the
  confirm handler uses; directories are not walked a second time.
- Offered only with HAVE_CONFIGFILE, the only first-run signal there is;
  dead have_bundled_assets logic removed.
- path_get_free_space: GetDiskFreeSpaceExA resolved at run time (not in
  the first Windows 95), GetDiskFreeSpaceA fallback, no <windows.h> on
  Xbox, statfs on Android (statvfs needs API 19), statvfs only on
  platforms known to have it instead of every __unix__ target.
- path_is_empty_directory: dotfiles (.nomedia, .DS_Store) do not count,
  the open call does not filter them outside Windows; documented.
- Ozone caches the Cancel label with the other footer labels instead of
  measuring it every frame the dialog is up.
- Pointer: one close, then confirm or cancel.
- Prompt text no longer promises overlays on platforms that skip them.
2026-11-07 23:49:58 +01:00
Craig Carnell bf93e5568a Add popup on first run if missing assets/controller profiles/core info files to download them 2026-11-07 23:49:58 +01:00
Craig Carnell e440219b71 make assets update optional on first run and enable by default for webOS 2026-11-07 23:49:58 +01:00
Eric Warmenhoven 404548376a Download various asset files on startup if necessary. 2026-11-07 23:49:58 +01:00
LibretroAdmin f507534f0d glsl: samplers nothing binds read a hardware frame's views in place
A GLSL pass reads unit 0, its input, through any sampler RetroArch does
not bind by name: every sampler links with the value 0, and only names
ending in Texture (and look-up textures' ids) are ever set. Shaders
such as jinc2, crt-hyllian and xbr-lv2 read the frame that way, as
s_p or decal, and so kept the copy; worse, such a sampler in a later
pass kept the copy for the whole preset. Such a name is now the frame
in the first pass and the pass before's output after it, as GL has it.

With two names for the frame in one stage (Texture and s_p, or Texture
and OrigTexture in the first pass) the rewrite put its helper in twice
and the program would not compile; it is now guarded, so the first
declaration's helper serves both, which read the same unit.

A function given the frame as a parameter had its body's reads
rewritten, but a define naming that parameter and expanded in the body
(crt-aperture's TEX2D reading tex) read the whole texture instead of
the view. Such a parameter now keeps the copy, unless its name is the
frame's own everywhere, as adaptive-sharpen's Source is.

glsl-shaders: 647 of 684 shader files rewrite (630 before), and 493 of
621 presets read in place (439 before); on llvmpipe, desktop GL and
GLES 3, every newly covered shader matches the copy byte for byte.
samples/gfx/glsl_rect tests the unbound name, two names in one stage,
a look-up texture's name left alone, and both define cases.
2026-11-07 23:49:11 +01:00
LibretroAdmin ecfbdbf3aa overlap_copy_allow: the deflate fast-table doubling and a test's distinct elements
The overlap-copy check flagged three same-base copies, all disjoint:
rinf_build doubles its fast and pfast tables in place, copying entries
[0, cur) to [cur, 2cur), and video_windows_test copies place_d[1].name
into place_d[2].name. memmove would only slow the table build the
in-place doubling was written to speed up, so they are allowlisted.
2026-11-07 23:29:47 +01:00
LibretroAdmin aa6a6bb75d netplay_e2e: say which side lost the stick when a lane fails
"states that never hash alike" failed once with the host seeing the
client's stick on none of 1513 frames, and did not fail again in 18
runs here. A failing lane now prints how often the client's own stick
moved, which shows whether its network gamepad was read at all, and
the device ids each core was given, which show whether the ports were
the analog joypads stick sync needs; the network gamepad's [Network]
and bind lines join the log lines it prints.
2026-11-07 23:27:16 +01:00
LibretroAdmin b5d1ae2acd glsl: passes reading the original read a hardware frame's views in place
A preset whose passes read the original frame (OrigTexture, or the
PassPrev that names it) kept its views' copies. Every pass that reads
the frame - the first, and each of those - is now rewritten for it,
swapped in together for a view's draw and given back after. A preset
reading earlier frames still keeps the copies those need, as does one
whose passes name a sampler nothing binds.

Of libretro's glsl-shaders presets reading the original, 45 of 94 now
read in place.
2026-11-07 23:19:01 +01:00
LibretroAdmin 315b03b24c gl2: GLSL presets read a hardware frame's views in place
A hardware frame's views went through a GLSL preset as copies, each
view drawn out of the frame into a texture of its own first. The
preset's first pass is now rewritten, once for its filter and wrap, to
read each view where it lies in the frame instead, as it would read
the copy: the same texels, filtering and edges, top row first.

The rewrite (glsl_rect.h) follows every read of the frame on the
source text - by its names, macros naming it or a read, and function
parameters every call gives it - into RARCH_frame(), which maps the
coordinate into the view and clamps or wraps it as the copy's sampler
would. Anything it can't follow, a pass reading the original frame or
earlier ones, a sampler it doesn't know, or linear filtering with
repeat, mirror or border keeps the copy. The rewritten program is
swapped in for each view's draw and given back after.

Of libretro's glsl-shaders, 633 of 684 files rewrite. On llvmpipe, 543
of the 612 cases that draw match the copy byte for byte; the rest
differ where a sample falls exactly on a texel edge or a filter weight
on a rounding step, by one step except in shaders that threshold such
values (hqx pass 1 linear, nnedi3 nearest).

samples/gfx/glsl_rect checks the rewrite and draws each filter and
wrap both ways on GLES 3 and 2; gl2_window_ui checks the views go
through it with no copy.
2026-11-07 23:11:16 +01:00
LibretroAdmin 201082c1ae Scanner: log the scanned folder for every manual scan
The 'Scanning content: "<dir>"' log line (and its CLI printf)
only ran on database-matched scans, so a plain manual scan with no
database logged nothing. Move it below both branches so every scan
that starts logs the folder it is walking.
2026-11-07 23:01:54 +01:00
LibretroAdmin 42c1439250 input: the pointer frame is not asked for joypad, analog or key reads
input_state_wrap_slow asked input_pointer_frame_read() before falling
through to the keys and the driver, for every device. It answers only
for the mouse, pointer and lightgun, so for the joypad, analog and
keyboard reads that make up nearly every slow read it was a call that
always said no. Those devices no longer ask.

Measured with callgrind over the frontend harness's input-cost lane:
10% fewer instructions in all, 16% in input_driver_poll and 17% in the
hotkey pass. Every lane gives the same results.
2026-11-07 22:51:56 +01:00
sonninnos 535ab2e8e3 Menu: Bind + remap list corrections
- Fix remap dropdown title with keyboard type
- Fix bind list item help texts
- Fix bind touchpad icons
2026-11-07 23:55:06 +02:00
sonninnos a0e1d959a2 Indent nits 2026-11-07 23:55:06 +02:00
LibretroAdmin d186c05fe1 scan: index a set fingerprint once when set_fpm1 equals set_fp1
A set with no clones has the same merged and split fingerprint.  Both
keys indexed the record twice, so the lookup returned it twice; with
its size equal to the archive's that read as two sized records and the
set fell through to a whole read.  The second key is now skipped when
it equals the first, which also keeps the index smaller.

The fingerprint's CRC list sits on the stack for sets of up to 64
members, so most archives no longer allocate for it.
2026-11-07 22:49:22 +01:00
davey e9759423d6 scan: no set fingerprint while an archive hides a member
rar names no member it cannot unpack (solid, encrypted, an older
packer), so a fingerprint taken over the members it does name would
claim a set that may not be all there.  Such an archive now has none
and goes on to its other passes.

7z and rar also name no folder, which would look the same, so the
scan's directory walk asks them to name one as zip does, ending in
'/'.  Nothing else sets the flag.
2026-11-07 22:49:22 +01:00
davey 4d320c445f scan: match arcade sets by a fingerprint of their member CRCs
The arcade databases key each set on the CRC of the whole
TorrentZipped archive, so the same ROMs packed any other way - zstd
or 7z, other names or order, a merged parent - never match.

An optional set_fp1 key holds the CRC-32 of the set's ROM CRCs,
de-duplicated, sorted and taken as big-endian bytes; set_fpm1 holds
the same for a merged parent.  The archive's one directory walk gives
its fingerprint, leaving out folders, empty members and cue or gdi
sheets; a member that stores no CRC leaves it without one.  Each
database whose cores open the archive's type and that carries the
keys is looked up through an index built like the crc one.  A hit
writes the record's entry, as a match on the archive's own CRC does,
and no archive is read whole for it.

An archive of several members is looked up by its fingerprint before
its size is.  An archive of one member is looked up by it only after
its member's CRC missed, so a first-member hit held for a cartridge
database still wins.

Where several records of a database share the fingerprint, as a set's
split and non-merged packings can, the one whose size is the archive's
is taken; where several have that size the archive's own CRC decides,
and where none does, the one named like the archive, else the first.

The readers read a TorrentZip set whole with no grant only while no
database that opens zip carries fingerprints, so the fingerprint
indexes of those databases are built before the readers start.  One
that cannot be built counts as carrying none: that database can match
the set only by its own CRC.  A database without the keys costs one
walk per scan; older versions skip the keys.
2026-11-07 22:49:22 +01:00
davey c62614ab13 libretro-db: carry set_fp1 and set_fpm1
libretro-database can give the arcade records a set fingerprint: the
CRC-32 of a set's ROM CRCs, de-duplicated, sorted and taken as
big-endian bytes, and the same for a merged parent.  Map both keys in
c_converter and the Lua converter so the rdb keeps them.  A converter
without these rows drops them, as it drops every key it does not map,
and readers that do not know them skip them.
2026-11-07 22:49:22 +01:00
LibretroAdmin 6070797cf1 rslc: a faster lexer; Direct3D bytecode declares only the constant buffers it reads
The lexer matches punctuators by their first byte, skips whitespace,
comments and splices in one pass, and hashes identifiers as it scans them;
node chunks are no longer zeroed ahead of use. The same output in about
38% fewer instructions.

DXBC declares a uniform block only when the code reads it, as fxc does, so
a binding the code never names is not one the root signature or pipeline
layout must have.

These files are now the same as parallel-gamecubewii's copy.
2026-11-07 22:45:53 +01:00
Davey Hughesandlibretroadmin e672f04d7b vulkan: add the required extensions a v1 core leaves out (#19989)
* vulkan: add the required extensions a v1 core leaves out

A core on context negotiation interface v1 makes its own device in
create_device() from the required_device_extensions RetroArch passes.
Under XR_KHR_vulkan_enable those include the OpenXR runtime's, and a
core that ignores the list (Flycast) leaves them out, which crashes
SteamVR in xrCreateSession.

The core now gets a vkGetInstanceProcAddr whose vkCreateDevice adds
the required extensions its create info left out, and a warning names
them for the core's developers. A core that makes its device some
other way under XR_KHR_vulkan_enable can't be vouched for, so headset
output is off for it.

The Vulkan validation job runs the v1 harness core with the required
extensions left out and checks for the warning.

* vulkan: make a v1 core's device the way RetroArch makes its own

The vkCreateDevice a v1 core gets now goes through
vulkan_context_create_device(), so its device also gets the temporal
upscaler's features and, under XR_KHR_vulkan_enable2, is made by the
runtime; a core that makes its device another way loses headset output
under either OpenXR interface. Required extensions the core's GPU lacks
are no longer forced on, which matters for a core that picks a GPU
other than the one offered, and a core that looks up
vkGetInstanceProcAddr through the one it was given still reaches the
wrapper.

The validation job checks that a core passing the required extensions
draws no warning, and that one resolving vkCreateDevice through its
own vkGetInstanceProcAddr lookup still gets them added.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-11-07 22:32:47 +01:00
LibretroAdmin 3c0c4a7e00 vulkan: read a mipmapped core image's views in place too
A preset's views were read where they lie only from a core image of one
level; one with mipmaps had each view copied out of its top level. Read
those in place as well, through a view of that level alone, made once
a frame and destroyed when the frame's slot comes round, since the core
need not keep the image past the frame. A core view made with chained
structures still takes the copy.
2026-11-07 22:09:04 +01:00
LibretroAdmin bffcbaf035 rjpeg: refill the 64-bit bit buffer in one step when no 0xFF is near
Every Huffman symbol tops up the entropy bit buffer, and the refill
took bytes one at a time: a compare against 0xFF, a variable shift,
an OR and a bound check per byte, about five bytes per refill on the
64-bit buffer.  On a 4K JPEG that was 12-15% of the decode.

The refill now loads the next eight bytes as one big-endian value
and tests all of them for 0xFF at once (a zero byte in its
complement).  When there is none - nearly always, in entropy-coded
data - it inserts exactly the bytes the byte loop would have taken:
from code_bits that is k = (FILL - code_bits) / 8 + 1, and the k bytes
read as one number land with one shift at the positions the loop
would have put them byte by byte.  The buffer, the bit count and the
read position come out identical, so everything downstream - markers,
restart intervals, the prefix decoder's checkpoints - sees the same
state.  An 0xFF among the eight, fewer than eight bytes left, the
24-bit prefix-decoding target and 32-bit bit buffers all keep the
byte path.  The value is assembled from bytes with shifts, which GCC
and Clang compile to one load and a byte swap, and which is correct
on either byte order.

Verified: decoded output identical to before - the 200-file corpus
(baseline and progressive, every sampling, gray, CMYK and Adobe RGB,
odd sizes), three 4K images (4:2:0, 4:4:4, progressive), restart
intervals of 1 MCU, 2, 7 and 1 rows in baseline, 4:4:4 and
progressive, and 500 fuzzed files - in both decode modes, on x86
under ASan/UBSan, on aarch64 and on big-endian ppc64 under qemu.
rjpeg, image_loader and image_avail sample tests pass; builds C89
-pedantic -Wextra without warnings, with clang, MinGW-w64, the NDK
(aarch64, armv7, i686) and PowerPC, and as C++.

3840x2160 decode: instructions -4.4% (4:2:0), -5.7% (4:4:4), -2.5%
(progressive); best time 26.4 -> 25.4 ms and 36.8 -> 36.3 ms.
2026-11-07 21:54:06 +01:00
LibretroAdmin def00963b2 gl2: set a view texture's filter and wrap only when they move
Each view's texture took four glTexParameteri calls every frame. Like
the frame's own textures, it now takes them when made and when the
preset's filter or wrap changes, kept per slot in one packed word.
2026-11-07 21:45:48 +01:00
LibretroAdmin 6dc20a5f5f rpng: inflate in 64 KB slices, halving the backend's history copy
After every call the inflate backend copies the last 32 KB of what it
just produced into its own 32 KB history ring, so that a later call's
back-references still resolve once the caller binds a new output
slice (rpng unfilters rows in place inside its ring, so the bytes
cannot be read back from there).  rpng fed it 32 KB slices, so every
decoded byte was copied a second time.

The slice is now 64 KB - the size the comment above it already argued
for ("must comfortably exceed the 32 KB deflate window and [be] small
enough to sit in L1/L2 alongside the window.  64 KB does both") - and
the history copy covers half the output.  A slice is still bounded by
contiguous ring space and by what the filters have consumed, as
before; each rpng_process_image step simply decodes up to 64 KB.  The
decoded bytes do not depend on how the output is sliced.

Verified: 428 decodes of 214 PNGs (4K screenshots, interlaced, RGBA,
8K, RGB/RGBA at 28 widths, and Paeth-on-every-row images for every
colour type and depth) give the same pixels as before in both output
layouts, under ASan/UBSan.  rpng, image_loader, image_decode_slice,
overlay_apng and image_avail sample tests pass; no new C89 warnings.

Decoding a 3840x2160 PNG screenshot: the history copy goes from 25.8 M
to 14.7 M instructions; mixed content best 38.5 -> 36.3 ms, median
42.8 -> 37.1 ms; smooth gradient best 9.75 -> 9.26 ms.  128 KB slices
measured only marginally better and would leave the comment's cache
argument behind on small-cache ARM parts.
2026-11-07 21:45:23 +01:00
LibretroAdmin 2aef16a86b rpng: SSE2 gate via retro_environment.h; Paeth step as a macro
Corrects the previous commit ("SSE2 paths enabled on MSVC x64"),
which overstated what it changed.  RetroArch's MSVC projects all
build through griffin, and griffin.c includes retro_environment.h
first - which defines __SSE2__ for MSVC's x64 and /arch:SSE2 targets
- so those builds already ran the PNG SSE2 paths.  The explicit
_MSC_VER && _M_X64 test only affected rpng.c or rpng_encode.c
compiled on their own.

Both files now include retro_environment.h and test __SSE2__ alone,
as pixconv.c and the audio converters do, so a file built on its own
takes the same path as the unity build.

That includes 32-bit MSVC with /arch:SSE2 - the default for x86 since
VS2012, and what griffin's Win32 configurations already get.  There,
only the first three __m128i arguments of a function can be passed by
value; the rest are rejected with C2719, and the decoder's Paeth step
took seven.  It is now a macro, so no vector is passed by value at
all.  Every input is read into a temporary before the result is
assigned, so the call sites that update a0/a1 in place are unchanged
in meaning; GCC and Clang inlined the function anyway and produce the
same code (decode instruction counts equal to within 14 per image).
This has not been compiled with MSVC itself.

Verified: 428 decodes of 214 PNGs - including 153 written with Paeth
on every row for gray 8/16, gray+alpha 8/16, RGB 8/16, RGBA 8/16 and
palette at widths 1-257 and Adam7 - give the same pixels as before in
both output layouts, under ASan/UBSan.  Both files compile against a
union-typed __m128i as in MSVC's headers.  rpng, image_loader,
image_decode_slice, overlay_apng and image_avail sample tests pass;
builds without new warnings with GCC (C89 -Wextra), clang, MinGW-w64,
the NDK (aarch64, armv7, x86_64, i686) and PowerPC, and as C++.
2026-11-07 21:39:57 +01:00
LibretroAdmin 396bc27fa2 gl2: write a screen window's views straight in too
With a screen in its own window, a software frame's views through a
preset fell back to uploading the whole frame and copying each view out
of it on the GPU, because the window copied its views from the frame.
The main window's draw now writes the windows' views straight into
their textures beside its own, and a window draws a view written this
frame as it is: one upload per view, no frame upload, no copies.

The frame is still uploaded whole, and the windows still copy, when the
main window draws no views or draws them straight from the frame.
2026-11-07 21:39:07 +01:00
LibretroAdmin b293291e9c rpng, rpng_encode: SSE2 paths enabled on MSVC x64
Both PNG files turned their SSE2 code on with #if defined(__SSE2__)
alone.  MSVC never defines __SSE2__ - SSE2 is simply part of the x64
baseline there - so RetroArch's MSVC builds, desktop and UWP, have
been decoding and encoding every PNG with the scalar fallbacks: the
row filters, the pixel conversions, all of it.  Most of formats/
(mp3, aac, opus, vorbis, h264, vp8, vp9, atrac3, the deflate encoder)
already names MSVC explicitly for this reason.

The gate is now defined(__SSE2__) || (defined(_MSC_VER) &&
defined(_M_X64)).  32-bit MSVC with /arch:SSE2 is deliberately left
out: it allows at most three by-value __m128i parameters (C2719) and
the decoder's Paeth kernels take more.  The SSSE3 runtime path stays
off on MSVC - neither of its compiler predicates matches - and MSVC
ARM64 is unchanged.  Nothing changes for GCC or Clang builds.

Not compiled with MSVC itself.  What was checked instead: both files
compile cleanly against an emmintrin.h whose __m128i is a union, as
MSVC's is, with every intrinsic declared as a plain function - so any
GCC vector-extension use (operators, subscripts, casts on __m128i)
would fail, and a planted one does; every SIMD region in the two
files keys off this one gate; the gate evaluates on for _MSC_VER with
_M_X64, off with _M_IX86 or _M_ARM64; both files build C89 -pedantic
-Wextra and with MinGW-w64 without warnings.

What it is worth, measured with GCC with and without the SSE2 paths
(the closest stand-in for the MSVC build before and after): decoding
a 3840x2160 PNG screenshot 22.8 -> 10.3 ms for a smooth image and
61.4 -> 38.4 ms for mixed content, a large RGBA image 47.6 -> 36.1
ms; encoding a 4K XRGB8888 / RGB565 frame 6% / 13% faster.  MSVC's
own auto-vectoriser may claw some of the scalar code back, so its
numbers will differ.
2026-11-07 21:32:28 +01:00
LibretroAdmin 2735a3c20c video_windows: build a display's key only where its name can match
Placement runs every frame, and each named screen or menu setting built
every listed display's key, each in O(count), to compare. A key starts
with its display's name, cut at most to its size less three, so only
displays whose name prefixes the setting are built and compared:
about 44% less per placement with every screen named.

The test now picks the second of two same-named displays, short and cut.
2026-11-07 21:28:28 +01:00
LibretroAdmin f884eb986d video: place windows by the frame's own clock sample
Window placement read the clock again for its move and UI timeouts;
take the sample video_driver_frame already made for the frame.
2026-11-07 21:28:28 +01:00
LibretroAdmin d5f65de5e0 gl2: draw another window's UI on that window's back buffer
The menu and notifications placed in another window went through an
offscreen RGBA image, cleared and drawn each frame, then blended over
the window. Laid out at the window's size, they are now drawn directly
on its back buffer while it is bound, with the main window's viewport
and projections left as they were.

The image is kept for a layout made at another size, and is made late,
for one frame, when the window turns out not to be drawable; the menu's
frame still runs once either way.

samples/gfx/gl2_window_ui covers both paths and the fallback.
2026-11-07 21:28:28 +01:00
LibretroAdmin ab4695f5f7 vulkan: draw another window's UI in that window's own pass
The menu and notifications placed in another window were drawn into an
offscreen image, splitting the main render pass to do it, then blended
over the window as a full-screen quad. When they are laid out at the
window's size, record them straight into the window's pass instead: no
image, no store and reload of the main pass on tilers, no extra blend.

The image stays for a layout made at another size and for the fallback
over the main window while the window is not up, and is made only then.
2026-11-07 21:28:28 +01:00
davey 24f9ff6d25 samples: hold the screen windows' rotation contract on the host
A turn of an Android display reaches RetroArch as a new size on paths
these harnesses already run on the host: the main window's content
rectangle or EGL surface, the second display's surface, and the window
layouts and display list the frontend publishes. Pin what each must do
with it, so a regression fails here and not only on a device.

- android_content_rect: a 1920x1080 to 1080x1920 swap asks one resize
  at the swapped size on the Vulkan context, and on the gl one, whose
  check_window() is now extracted too, with egl_get_video_size()
  stubbed.
- android_second_surface: the same surface at a new size is one
  GEOMETRY at that size, the same generation, no CLOSE, still shown;
  a window kept across a driver reinit meanwhile is taken at the size
  last drawn, and the next driver's first poll brings the new size.
- video_screens: a window drawn again at swapped dims lays the menu out
  at that size and maps touches through the new layout from the first
  read; a display whose size changes under the same name keeps its
  placement and does not ask again for a move that was given up; a
  menu waiting for a turned display's window lays out at the size
  listed now.
2026-11-07 21:28:27 +01:00
davey 3f12c19d53 menu: drop a press held through the menu opening
A touch or mouse button already down when the menu opened acted on the
menu. A finger held on the game as the menu toggle fired landed on
Resume once let go and shut the menu again, and a held right button
cancelled on the menu's first frame. This happened in every window, a
single window included.

Such a press is now dropped until it lifts, as one held across a change
of the menu's window is: no POINTER_DOWN, POINTER_UP, OK on release or
cancel comes of it, and the next press works as any. The cancel cover
takes a pointer's back, Android's Back key among them. A cancel already
down in the window the menu moves to is held back the same way. Only a
real open counts: retroarch_menu_running() with the menu already up, as
after a failed load, changes nothing. A press the menu held as it closed
no longer fires its release on the next open either.
2026-11-07 21:28:27 +01:00
davey 92d2d1393d menu: read touches from the window the menu is drawn in
With the menu drawn in the Presentation, it reads that window's first
touch, over the window at the menu layer's size, and nothing from the
main window; RGUI maps the touch through its framebuffer as it is fitted
to the layer. An input overlay, which stays in the main window, no
longer turns the menu's touches off there. The press and release bounds,
the drag and swipe thresholds, the wheel and the on-screen keyboard's
hit test use the layer's size too.

A press held while the menu changes window is dropped until it lifts: no
tap, release or cancel comes of it in either window. With the menu in
the main window nothing changes.
2026-11-07 21:28:27 +01:00
davey d94e1409cd android: publish the mouse and every touch to the frontend
The touches on the main window and on the second display go to the
frontend's pointer publication each poll, raw, each with the window it
is in, and the publication answers the pointer, the screen pointer,
the second display's pointer and a lightgun aimed by touch. The mouse
stays the driver's: its motion and wheel are taken by each read, as
before.

Every answer is as it was; the pointer_frame test checks each one the
publication gives against the driver's own answers before this change.
Three things differ. Binds to the mouse's left, right and middle buttons
now work: they never fired on Android. And a touch is placed at the
first read after the poll instead of as its event came, so a finger held
still while the viewport changes follows the change at once.

And a tap on a main window that shows no screen is on nothing: it fires
no lightgun button there, while a tap in the letterbox still does.
2026-11-07 21:28:27 +01:00
davey 1e0f4db5d9 input: let a driver keep its own pointer answers
Android's input driver is to publish its touches and mouse through the
frontend's pointer publication, so that each touch carries the window
it came from, without changing any answer a core or the menu gets
from it. Five flags let a driver ask for that, and change nothing for
the drivers that publish today: a touch pressed only inside what it
is read against; touches in a screen's own window as a second list;
the mouse answered by the driver, whose motion and wheel go with each
read; touch indices as slots that keep their place, with no mouse
standing in; and pointer ids the frontend does not hold left to the
driver.

The publication also holds 32 touches, and gives the menu a touch in
its own window over that window's client area.
2026-11-07 21:28:27 +01:00
davey 9e97caabae video: show the RetroArch logo in a window with nothing to show
When the menu's display cannot take a window of its own and the game
is shown elsewhere, RetroArch's own window has nothing to draw until
the menu opens there. It now shows the RetroArch logo from the
monochrome icon set, centred on black, drawn as one quad each frame.
Without that file or without widgets it stays black.
2026-11-07 21:28:27 +01:00
davey e9dd4b522d vulkan: draw the menu and notifications in another window
When placement puts the menu or the notifications in the Presentation,
they are drawn into one offscreen image at that window's size, between
two halves of the frame's pass, and composited over the window's
screens. While the image is drawn, the display backend places its quads
and meshes against the image's height, not the swapchain's.

A window that shows the UI takes an image on every frame it has UI to
draw, not only on a new frame with views: a paused hardware core repeats
its frame under the menu, and a core without a map has no views. On a
repeat its screens are drawn again from the last frame. A view that only
this window draws, and that the last new frame did not copy, is left out
rather than turning the main window's views off. A window with only UI
publishes an empty layout, so touches there miss the screens.

A window with nothing to draw this frame, or with no free image, keeps
its last image. Only a window that is gone sends the UI to the main
window, in the same pass, unless the layer is still waiting for its
window. With HDR the layers stay in the main window. With both layers in
the main window the frame is drawn as before; the menu's draw moves into
a helper both targets share.
2026-11-07 21:28:27 +01:00
davey 207aa0443e gl2: draw the menu and notifications in another window
When placement puts the menu or the notifications in the Presentation,
they are drawn into one offscreen image at that window's size and
composited over its screens. A window that shows them is drawn on every
frame it has UI to draw, with or without content and views, so the menu
keeps answering under a paused core; with nothing to draw it keeps its
last image.

If that window cannot be drawn in a frame, the image goes over the main
window in the same frame, unless the layer is still waiting for its
window to appear. Without the views' programs the layers stay in the
main window. With both layers in the main window the frame is drawn as
before.
2026-11-07 21:28:27 +01:00
davey 208da32d26 video: lay the menu and notifications out at their own window's size
The menu and the notification widgets each lay out at the size of the
window they are drawn in, which can now differ from the main window's:
the runloop asks for each layer's size, and under the threaded wrapper
the widgets' worker takes the notifications' size from the frame it
draws. With both in the main window nothing changes.
2026-11-07 21:28:27 +01:00
davey 29d8db144e video: hand a kept window to the next driver across a reinit
A driver teardown let the second display's Presentation go. When
content closed and the driver reinited, the hide could reach Android
before RetroArch's Activity arrived on that display, which then showed
neither.

A context's window ops can now keep and take a window. At teardown,
gl2 and vulkan keep each window their context can keep: its EGL or
Vulkan surface goes, and the Presentation stays up with its last image.
The next driver takes it at init and publishes it with no screens, so
it still counts as shown on its display and the close's keep holds it.
Placement lets it go as before. A window nobody took goes once the
next driver is up, or at shutdown.

gl2 no longer draws a kept window, so it keeps its last image as it
does on vulkan.
2026-11-07 21:28:27 +01:00
davey 621b600e6f video: a window counts as shown only from a frame after its last change
Under the threaded video wrapper the app thread runs ahead of the
video thread, so for about a frame after a snapshot lets the
Presentation go or moves it, the newest published layout still names
the old display. A make decided then completed at once and RetroArch
left before the new Presentation drew; a keep could be set on the
window being retired.

Each snapshot now carries a serial, and each published layout the
serial of the snapshot it was drawn from. A window counts as shown only
from a layout drawn at or after the last snapshot that asked for it
differently, so make before break holds. A snapshot the wrapper takes
back undrawn needs nothing special: the later one that is drawn counts.

A keep only holds a window that is up. A driver with no window no longer
makes one for it, which at content close covered RetroArch's display
with a Presentation that was never drawn.

The frame also reads shown, the clock and the menu state once, for
placement and the UI targets alike.
2026-11-07 21:28:27 +01:00
davey e41f3ed06b video: place screens, the menu and notifications on displays
With Separate Screen Windows on, video_windows_place() decides each
frame, from the display settings, the published display list, the
content and the menu, where each screen, the menu and the notifications
go, and which display RetroArch's own window is on. It replaces the
route, which sent only screens 2 and up to a Presentation.

Auto no longer depends on where RetroArch was started: Game Screen 1
takes an external monitor, else the default display; Game Screen 2 the
built-in second panel, unless a monitor took Game Screen 1. A display is
in use only while it shows a screen of the running game or the open
menu. RetroArch's window goes to a display the game may use that refused
a Presentation, else to the lowest shown screen's, else, with no game,
to the menu's. Any other display in use gets the one Presentation.

RetroArch moves only when content starts or closes, a setting or the
display list changes, or a display refuses. A change of the core's map
within two seconds of a start is still the start; a later one only
re-routes the screens. Replacing content is a close and a start, decided
once the new content is up. Opening or closing the menu never moves
anything. Moves are made before they break: a display RetroArch leaves
that still shows something gets its Presentation first, and one it moves
onto keeps its Presentation, not drawn, until RetroArch has drawn there.
A move that cannot start or does not arrive within two seconds gives
that display up until the display names change.

Placement runs whenever windows are on, with or without content or
views. A UI layer whose Presentation is being made waits for it up to a
second, the frame the menu opens included, then falls back to the main
window. A main window that shows no screen reports touches off-screen,
and the menu maps them over the whole window.
2026-11-07 21:28:27 +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 6d99b615d4 android: tell native code which display is the default one
Each published display now says whether it is Android's
DEFAULT_DISPLAY. On a dual-screen handheld both panels report the
same unknown connection, so this is the only way to tell the main
panel from the second one when choosing a display automatically.
2026-11-07 21:28:26 +01:00
davey 78dda5539e android: drop the second display's key relay and focus count
The second display's Presentation never has the focus now, so it never
receives a key or a stick move and never reports a focus change. Its key
and motion relay, the ring that carried them to native code, its focus
commands and its part in the focus count go: RetroArch's focus is its
Activity's, as before the second display existed.
2026-11-07 21:28:26 +01:00
davey 8ccf91badb android: take the focus back after RetroArch moves to another display
When the focused Activity moves between displays, Android sends the new
display's focus gain and the old display's loss in either order. If the
loss comes last, the Activity is never told it has focus again, so
RetroArch stays paused and stops drawing.

After a move that leaves it unfocused, the window is made unfocusable
until Android has taken the focus, then focusable again, which makes
Android send the gain. Under load the window manager can apply a short
toggle's set and clear back to back, and the focus never moves, so the
flag is held until that focus event, or a second at most. A gain ends
the move only once it has outlasted a traversal, so a loss that follows
it still refocuses.
2026-11-07 21:28:26 +01:00
davey 218637a061 android: let RetroArch move its Activity onto another display
Native code can now ask Java to move RetroArch's Activity onto a
display of the published list. The request is posted to the UI thread
and never waited on. Java starts the running Activity again with
setLaunchDisplayId and a copy of its own intent, so the same content
is seen and nothing restarts; Android then moves the Activity in place.

When the move cannot start (before Android 8, a display that has gone,
or a launch Android refuses), Java logs it once and raises a flag the
main thread reads on its next frame.
2026-11-07 21:28:26 +01:00
davey 254c6ab2c4 android: publish refused displays and display models
A display whose Presentation failed is now published as refused instead
of invalid. It stays a valid target, so Auto and a saved name still
point at it and RetroArch can move onto it; only a Presentation is kept
off it. A list that changes only in which display is RetroArch's own
keeps it refused.

Each display also carries its DeviceProductInfo name (Android 12 and
later), which the menu shows next to the display's name.
2026-11-07 21:28:26 +01:00
davey 85f78b4084 video: name a platform's displays the way a setting will
A screen will name its display by the platform's name for it, which
says which port a monitor is on. video_windows_display_key() gives
that name, with " 2" or " 3" added for a second or third display
listed under the same name, so each one can be told apart and saved.
A display also carries its product name, for the menu, and whether a
Presentation failed on it.
2026-11-07 21:28:26 +01:00
davey ee03793ab6 samples: video_views core can change its views map at a set frame
video_views_test_map_change names a map the core changes to once
video_views_test_map_change_frame frames have run; none clears the map
with an empty one. A harness can then check how the frontend places a
game whose map changes early in a start or later on. It is off by
default.

The Vulkan modes keep their images at the first frame's size, so there
a change that would resize the frame is refused and logged.
2026-11-07 21:28:26 +01:00
davey 2242e5f4f0 android: keep the pad on RetroArch after a tap on the second display
The second display's Presentation could take the focus, so a tap there
gave its display the keys. When the content closed and the Presentation
went, the keys stayed on that display and reached the app under it: on
the AYN Thor the pad drove the launcher's bottom-screen activity instead
of RetroArch's menu. Taking the focus back with moveTaskToFront did not
help, since RetroArch's task was already in front on its own display.

The Presentation is now never focusable. Touches still reach it; keys
and sticks always go to RetroArch's Activity.
2026-11-07 21:28:26 +01:00
davey 579027cb3f android: keep running when the display density changes
Changing Android's Display size setting, or RetroArch's window going to
a display of another density, relaunched the Activity and RetroArch
exited. The Activity now handles a density change itself.
2026-11-07 21:28:26 +01:00
davey 088da6b2bc android: read a display's size without the Activity's adjustments
From API 31 a Display obtained with the Activity's Resources reports
RetroArch's own window, so the size published for a display could be
RetroArch's window's, and it changed when that window went to another
display. A Display with no Resources attached reports the display's own
logical size.
2026-11-07 21:28:26 +01:00
davey c523a205aa menu: a uint dropdown pick sets the value its entry was listed with
A uint setting's list can leave values out: Mouse Index lists only the
mice there are, and Keyboard Index lists only the keyboards there are
and the one chosen now. The pick was worked out from the entry's
position, as the minimum plus whole steps, so a value left out shifted
every pick after it: with mice at #1 and #3, picking #3 set #2. Each
entry now carries the value it lists, and the pick sets that one.

The network settings harness passes each entry's entry_idx, which
carries its value, as the menu does, and picks a Mouse Index past an
index with no mouse and a Keyboard Index past an index with no keyboard.
2026-11-07 21:28:26 +01:00
LibretroAdmin 4269af1c0c rpng: 8-bit RGB and RGBA rows converted with SSSE3 where the CPU has it
Every decoded row is converted from PNG's byte order into the 32-bit
words the frontend uses.  For 8-bit RGB - every screenshot RetroArch
writes - SSE2 has no byte shuffle, so four pixels cost four
whole-register shifts and masks to spread the triples into words,
plus a mask-and-shift R/B swap for ARGB output.  SSSE3's pshufb picks
each output byte straight out of the load: one shuffle and the alpha
OR per four pixels.  8-bit RGBA to ARGB is likewise one shuffle in
place of the masked swap.  The bounds and scalar tails are the SSE2
versions', so the output is identical.

A stock x86-64 build has no flag enabling SSSE3, so where the
toolchain can build a function for it without one - GCC 4.9+, or any
compiler with __has_attribute(target) and <tmmintrin.h>, the same
predicate encoding_deflate.c uses for its CRC32 hash - the choice is
made once per decode in rpng_process_init from cpu_features_get().
With -mssse3 it is made at compile time.  Without SSE2, on ARM and on
other targets nothing changes.

rpng.c now calls cpu_features_get() on those x86 builds, so a build
compiling rpng.c must also link features/features_cpu.c.  Every
RetroArch build (Makefile.common, which Makefile.win includes, and
griffin) and every libretro-common sample that builds rpng.c already
does.

Decoded pixels are identical to before - 5 large PNGs (4K screenshots,
interlaced, RGBA, 8K) and RGB and RGBA images at 28 widths from 1 to
1919, in both output layouts, 122 decodes - with the SSSE3 path chosen
at run time (also under ASan/UBSan), compiled in with -mssse3, and
forced off.  rpng, image_loader, image_decode_slice, overlay_apng and
image_avail sample tests pass.  Builds without new warnings with GCC
(C89 -Wextra), clang, MinGW-w64 and the Android NDK (x86_64 and
aarch64), and as C++.

Decoding a 3840x2160 PNG screenshot: smooth gradient 11.6 -> 10.0 ms.
The conversion alone goes from 4.0 to 1.7 ms per frame; on content
whose decode is bound by the filters or by streaming the output to
memory the difference is within noise.
2026-11-07 21:27:10 +01:00
LibretroAdmin d21b3f6bef rinflate: Huffman fast tables filled by doubling, not strided stores
Every dynamic block rebuilds three decode tables, and each has a
2048-entry fast[] and pfast[] pair.  A code of length len owns every
entry whose low len bits are its bit-reversed code, so the build
stored it at each of 2048 / 2^len slots, one strided store pair per
slot - 4096 stores per table, three tables per block.  Smooth image
data compresses into many short blocks, and on a 4K gradient
screenshot that was a tenth of the whole PNG decode.

The tables are now filled as libdeflate does it: codes are placed
length by length into a table exactly 2^len entries long, and before
each next length the table is doubled by copying its lower half up,
which lays down every repeat of every shorter code at once.  Most of
the work becomes a handful of memcpy calls.  The packed-entry
computation moves into rinf_pfast_entry(), unchanged.

The tables come out entry for entry as before.  In a complete code
every final slot belongs to exactly one code - a short one, placed at
its own length and afterwards only copied, or a long one, whose
prefix slot is zeroed - so a slot's leftover from the previous block
is always overwritten; an incomplete code is still zeroed first, and
the copies keep its gaps zero.

Verified: 300000 builds into one never-cleared rinf_huff - random
complete, incomplete, single-symbol and junk length sets for all
three tables, 86% of them accepted - leave the struct and the return
value byte-identical to before after every build, also under
ASan/UBSan.  zlib streams of six kinds at levels 1, 6 and 9 decode to
the original for every input/output chunk pairing (840 runs) with the
same hashes and status as before, and 6720 corrupted-stream decodes
match the old ones exactly.  Decoded pixels of five PNGs are
unchanged.  All rzip, archive, stream, rpng and encodings sample tests
pass; builds C89 -Wextra without new warnings, for aarch64, armv7 and
PowerPC.

Decoding a 3840x2160 PNG screenshot: gradient 13.8 -> 11.8 ms, mixed
content 41.2 -> 38.7 ms.
2026-11-07 21:17:10 +01:00
LibretroAdmin 36c7a81ee5 rinflate: hand back to the fast loop after a careful-path copy
The careful (symbol-at-a-time) path exists for the moments when a
symbol or a copy straddles a buffer edge, and at the top of each of
its iterations it checks whether the margins are comfortable again
and, if so, jumps back into the fast loop.  But after finishing a
match copy it fell straight through to decoding the next symbol
itself.  If that symbol was another match, the length/distance steps
ran here, set copy_active, and the next pass through the hand-back
check saw an active copy and stayed - so the careful path only ever
gave control back after a literal.

Smooth image data is long runs of back-to-back matches with no
literal between them.  One match straddling an inflate slice edge put
the rest of its run on the careful path, whose in-buffer copy for
short distances is a byte loop - and at distance 3, RGB's period,
each byte waits on the store of the byte three before it.  Decoding a
3840x2160 gradient screenshot, 57,583 of the careful path's 58,363
iterations were that: nearly every match in the image.

After a completed copy the loop now goes back to the hand-back check
before decoding anything.  That only re-runs a check the loop already
makes, so what is decoded is unchanged.

Verified: zlib-compressed noise, gradient, checker, short-period,
zero and mixed data at levels 1, 6 and 9 decode to the original for
every pairing of input chunk (1, 7, 64, 4096 bytes, whole) and output
chunk (1, 3, 258, 273-275, 1000, 32K, 64K, 4M), 840 runs, with the
same output hash and status as before; 6720 decodes of corrupted
copies of those streams give the same bytes, length and status as
before, also under ASan/UBSan.  Decoded pixels of five PNGs (4K
screenshots, interlaced, RGBA, 8K) are unchanged in both output
formats.  All rzip, archive, stream, rpng and encodings sample tests
pass; builds C89 -Wextra without new warnings, for aarch64 and armv7.

Decoding a 3840x2160 PNG screenshot: smooth gradient 39.1 -> 17.9 ms
(instructions -21%, the rest was the serial byte loop's latency);
mixed content about -7%.
2026-11-07 21:10:11 +01:00
LibretroAdmin d1b5629761 Scanner: find the Jaguar CD session 2 track in the one parse
Follow-up to "Scanner: Match Jaguar CD discs by CRC".

When a sheet had no data track, the fallback parsed the whole sheet
a second time with that track forced to data. That re-reads the cue
and reopens every track file - a Redump Jaguar CD has one file per
track, so ~20+ extra opens per disc during a scan.

Note the first track of session 2 (while no data track has been seen)
during the one parse instead: its start index, its end (next track's
index in the same file, or the file's end), its path and its open
stream, which the CRC pass then hashes without a fresh open, as a
data candidate's is. The force_track parameter goes away.

Same answers as before on 13 sheets (one file per track, single bin,
shared files, Enhanced CD, data before/after session 2, missing file,
CD-i, REM comments), ASan/UBSan clean. On a 24-track Redump-style
sheet: 48 -> 24 track opens, ~100 -> ~47 us per sheet.
2026-11-07 21:00:15 +01:00
Rob Loach 834f6aee6e Scanner: Match Jaguar CD discs by CRC 2026-11-07 21:00:15 +01:00
LibretroAdmin a859096242 database_scan_test: slow the unsized TorrentZip whole reads to 500 ms
"unsized TorrentZip sets: readers stop reading whole" failed twice on
the four-core runner with 16 of 24 zips read whole, four rounds of the
readers against three allowed: the sanitized scan ruled on the first
set later than the 150 ms the readers took over each read. Forced to
four readers here, the lane read 7 to 12, at the limit. A whole read
now takes 500 ms, so the same delay in the scan lets fewer rounds
through: 6 to 9 over six runs at four readers. The limit stays at
three rounds.
2026-11-07 20:56:39 +01:00
LibretroAdmin 761cfeefe5 rdeflate: the lazy parser keeps its state in locals
The lazy parse loop (levels 4-9, the screenshot encoder's level 6)
read and wrote pos, nsyms, the fixed-Huffman bit count and the
pending match through the struct at every position.  Each literal it
records stores into the symbol buffer and the frequency table, and as
far as the compiler can tell any of those stores may have changed one
of the fields, so every one was reloaded after every symbol.  On
noisy input nearly every position is a literal.

The loop now holds that state, and the tuning values and table
pointers it uses, in locals and writes them back when it ends.
Literals are recorded against the locals; matches - where the
per-symbol cost is not the issue - hand nsyms and the bit count to
rd_record_match() and take them back.  Nothing the loop calls reads
the parse state: the match finder takes the position as an argument
and the insert paths touch only win, head and prev.  The decisions
and their order are untouched.

Compressed output is byte-identical to before over the level 0-9 x
input chunking x six-input matrix, zlib-wrapped and raw, with the CRC
and the shift-xor hash, on x86 (also under ASan/UBSan) and aarch64
under qemu, and over a 3 GiB stream at level 6.  rzip, rpng, archive
and encodings sample tests pass; builds C89 -Wextra without new
warnings, for aarch64, armv7 and PowerPC.

screenshot_io_bench, 3840x2160 BGR24 PNG: noise instructions -10.0%
(8.53 G -> 7.68 G); gradient unchanged.  On the x86 box measured the
wall time moves within noise - the remaining cost there is cache
misses in the hash tables, and the reloads were L1 hits an
out-of-order core absorbed.  In-order cores are where the cut should
show.
2026-11-07 20:55:32 +01:00
LibretroAdmin 468dffe605 rpng_encode: Paeth filter on 16 bytes at a time, without widening
The encoder tries every filter on every row, and Paeth was the
costliest of them: SSE2 and NEON both widened each byte to 16 bits
to form pc = |a + b - 2c|, so a step covered only 8 bytes, with
unpacks going in and a pack coming out.

The predictor now stays in bytes.  pa = |b - c| and pb = |a - c| are
byte absolute differences.  pc needs nine bits only where b - c and
a - c have the same sign, and there it equals pa + pb, which is no
smaller than either; saturating it at 255 therefore cannot change how
it compares with pa or pb, which is all the predictor asks of it.
Where the signs differ pc is |pa - pb| and fits.  A zero difference
makes both forms equal, so its sign does not matter.  The selection
is unchanged: a unless pa > pb or pa > pc, then b unless pb > pc.
SSE2 builds the unsigned compares from saturating subtracts; NEON has
them, and vabd/vqadd, natively.  Each step now covers 16 bytes.

Exact: the SSE2 and the NEON predictor (aarch64 under qemu) match
the scalar rpng_paeth_pred() for all 2^24 inputs.  PNG output is
byte-identical to before for BGR24, XRGB8888 and RGB565 across widths
1-1920 and both pitch signs, on x86 under ASan/UBSan and on aarch64.
rpng sample tests pass; builds C89 -Wextra without warnings, as C++,
for aarch64, armv7 NEON and PowerPC.

screenshot_io_bench, 3840x2160 BGR24 PNG: gradient instructions
-7.3% (2.58 G -> 2.39 G), median 53.4 -> 49.8 ms; noise -2.2%.
2026-11-07 20:39:12 +01:00
LibretroAdmin 24d7f4cc84 rdeflate_hash_test: hand rd_key3 a word to read
0ca986d reads the CRC hash key with one 4-byte load and masks off the
fourth byte, which the window's 8-byte margin makes safe in the
compressor. The test still built its keys in a 3-byte array, so ASan
caught the load running one byte past it and the core samples group
failed. The key buffer is now 4 bytes, the fourth filled with noise
the mask has to drop.
2026-11-07 20:33:13 +01:00
LibretroAdmin 1525c3bb29 MCP: keep the token owed on exit, stdio SIGPIPE without network init
Follow-up to "MCP: fix token length, SIGPIPE, stdio EOF, cleanup".

- input_driver: the generated token is saved at once, but
  config_save_file refuses while overrides are active (and is skipped
  with no config path). The modified flag was dropped with it, so a
  token made with a core's overrides loaded was lost on exit even with
  config_save_on_exit. Set the flag as before, then save now.

- mcp_server stdio: ignore SIGPIPE directly instead of calling
  network_init(). stdio uses no sockets; network_init brings up
  WSAStartup on Windows and the network stack on consoles for nothing.

- mcp_structured_record: take strlen(seps) once, not per column, and
  guard an empty seps string, where n - 1 would index far out of range.
2026-11-07 20:32:05 +01:00
Rob Loach a299ebbe23 MCP: fix token length, SIGPIPE, stdio EOF, cleanup 2026-11-07 20:32:05 +01:00
LibretroAdmin 368fe742f5 rdeflate: the window slides without rewriting the hash chains
Every 32 KB of input the encoder slides its window down, and with it
every link in head[] and prev[] - 64K entries, 256 KB - was rewritten:
dropped by 32768, or zeroed when it fell out of the window.  On a 4K
screenshot that is some 760 slides and 190 MB of table traffic per
capture, about an eighth of a smooth image's encode; on compilers
that do not vectorise the loop (MSVC, the older GCCs consoles build
with) it is worse.

The links are now stored as position + hoff instead of position + 1.
A slide raises hoff by the slide amount, which drops every stored
position by it without touching the tables.  A link that slid out of
the window reads back as a negative position, below any search limit,
so a chain walk ends on it exactly where it ended on the zero the
rewrite used to put there; the zero calloc leaves still reads back as
"empty".  rd_insert's "chain exists" answer is likewise unchanged, so
the parse is the same.  So the stored values never leave int32 range,
the old rewrite still runs once hoff passes 1 GiB, and hoff restarts
from 1.

The chain walk keeps the position itself rather than the one-based
link, so the per-candidate "cur != 0" test goes away with the rewrite.

Compressed output is byte-identical to before: the level 0-9 x input
chunking x six-input matrix, zlib-wrapped and raw, with the CRC and
the shift-xor hash, and again with the rebase forced at every slide
and every third slide (RD_HOFF_REBASE overridable for that), also
under ASan/UBSan; and a 3 GiB stream at levels 1 and 6, which crosses
the real rebase twice.  rzip, rpng, archive and encodings sample
tests pass; builds C89 -Wextra without new warnings, for aarch64,
armv7 and big-endian PowerPC.

screenshot_io_bench, 3840x2160 BGR24 PNG: gradient instructions
-11.5% (2.92 G -> 2.58 G), best 45.2 -> 40.7 ms, median about -10%;
noise -2.3%.
2026-11-07 19:57:47 +01:00
LibretroAdmin 0ca986d2d4 rdeflate: CRC hash key read with one load, not three
Every position the encoder inserts into a hash chain - on screenshot
content, nearly every position - first builds the 24-bit key for the
CRC32 hash from three byte loads, two shifts and two ors.  That was
the single most expensive line in the encoder: 522 million
instructions for three 4K captures, on smooth and noisy images alike.

On little-endian targets the key is now one 32-bit load with the top
byte masked off, which is the same value.  The fourth byte it reads
is always inside win[]: a key is only taken where three bytes remain
before win_len, and win[] already carries an 8-byte margin past its
largest win_len for the word compares in rd_longest_match().
Big-endian targets keep the byte-wise form, and builds without a CRC
hash (shift-xor only) are untouched.

Compressed output is byte-identical to before over the level 0-9 x
input chunking x six-input matrix, zlib-wrapped and raw, for each
hash build: runtime-detected SSE4.2, -msse4.2, and aarch64 +crc
under qemu; valgrind memcheck clean over the same matrix.  rzip,
rpng, archive and encodings sample tests pass; builds C89 without
new warnings, for aarch64 with and without +crc, armv7, and
big-endian PowerPC.

screenshot_io_bench, 3840x2160 BGR24 PNG: gradient instructions
-11.3% (3.29 G -> 2.92 G), median about -10%; noise -4.0%, best
368 -> 351 ms.
2026-11-07 19:32:20 +01:00
LibretroAdmin 7b44ddac58 rdeflate: the lazy parser skips the match finder on an empty chain
rd_insert() hands back the chain head it just linked for a position,
and the greedy parser (levels 1-3) already used it to skip the match
search when there is no earlier occurrence.  The lazy parser - levels
4-9, which includes the level-6 screenshot encoder - threw it away
and called rd_longest_match() at every position, which reloaded the
head from prev[] and returned 0 when it was empty.  On noisy input
that is about half of all positions: a full call, register saves and
the struct loads of the prologue, to learn there was nothing to do.

Both parsers now pass the head in and only call when a chain exists;
rd_longest_match() starts its walk from it instead of re-reading
prev[].  An empty chain returned 0 with no side effects before, so
the parse - and the stream - are unchanged.

Compressed output is byte-identical to before over the level 0-9 x
input chunking x six-input matrix, zlib-wrapped and raw, also under
ASan/UBSan; rzip, rpng, archive and encodings sample tests pass;
builds C89 without new warnings and for aarch64.

screenshot_io_bench, 3840x2160 BGR24 PNG: noise instructions -10.3%
(10.37 G -> 9.30 G), best 393 -> 380 ms, median about -4%; gradient
unchanged.
2026-11-07 19:24:01 +01:00
LibretroAdmin e67ddc9a8a rpng_encode: XRGB8888 and RGB565 rows converted with SSE2/NEON
A screenshot of a core's own frame - no GPU readback, no rescale -
hands the encoder XRGB8888 or RGB565 rows, which it turns into PNG's
R,G,B per row.  Both conversions ran a pixel at a time, ahead of
filters that are all vectorised; the BGR24 readback path got the same
treatment in the previous commit.

SSE2 builds each pixel as a 32-bit lane 0x00BBGGRR (R,G,B,0 in
memory) and then closes the gaps without a byte shuffle: in each
64-bit half the odd lane moves down a byte onto the even lane's zero,
and the upper half moves down to follow the lower one.  Four pixels
land in the first 12 bytes; the last four are rewritten by the next
store or the scalar tail.  XRGB8888 gets its lane from a 16-bit
rotate and two masks, RGB565 from the same shifts and masks as
pixconv_rgb565_to_xbgr8888.  NEON reads XRGB8888 as four planes (vld4,
little-endian only) and expands RGB565 with one vsri per channel,
which is exactly the top-bits replication; both store with vst3.
Other targets keep the scalar loops.

PNG output is byte-identical to before: 12 widths from 1 to 1920,
padded and negative pitches, both formats, ASan/UBSan clean.  The
conversions match the old loops on SSE2, on NEON (aarch64 under qemu)
and on the scalar path at every width 0-300 and, for RGB565, for all
65536 input values, and never write past the row.  rpng sample tests
pass; builds C89 -Wextra without warnings, as C++, and for aarch64
and armv7 NEON.

Per 3840x2160 frame, conversion alone: XRGB8888 3.75 -> 2.45 ms,
RGB565 9.77 -> 4.00 ms.
2026-11-07 19:17:49 +01:00
libretroadmin 7e92aa944e oss: RetroFW writes whole 16-byte DMA units
RetroFW's jz4760 OSS driver moves a node to the codec in 16-byte DMA
units: it drops a write's remainder, and a write under one unit starts
a transfer of none. The threaded pipeline writes any number of frames,
so most writes lost one to three frames - static at its rate of writes -
and a short one could leave the device playing nothing it was given.
On RetroFW a write now goes out in whole units, its remainder held to
lead the next one.

samples/audio/oss_report models that DMA; built with RETROFW=1, which
CI now does, it checks every node is whole units and plays in order.

Fixes #20006
2026-11-07 19:06:01 +01:00
LibretroAdmin 391dbc91b1 rpng_encode: BGR24 rows swapped to RGB with SSE2/NEON
A BGR24 screenshot - the GPU-readback path - has each row turned
into PNG's R,G,B order before filtering.  That swap ran a byte at a
time, ahead of filters that are all vectorised, and was two thirds of
rpng_encode_row: about 12 instructions a pixel, 83 million per 4K
capture.

SSE2 has no byte shuffle, but this swap needs none.  A 16-byte load
that starts on a pixel boundary holds every byte's partner two places
away in a pattern that repeats every three bytes, so two whole-register
byte shifts and three fixed masks build it; each step completes four
pixels and steps 12 bytes, its last four bytes rewritten by the next
store or the scalar tail.  NEON loads the channels as planes (vld3)
and stores them back crossed.  Other targets keep the scalar loop.

PNG output is byte-identical to before: 17 widths from 1 to 1920 x 4
heights x smooth and noisy content x both pitch signs, ASan/UBSan
clean.  The swap matches the byte loop at every width 0-300 on SSE2,
on NEON (aarch64 under qemu) and on the scalar path, and never writes
past the row.  rpng sample tests pass; builds C89 without warnings and
for aarch64 and armv7 NEON.

screenshot_io_bench, 3840x2160 BGR24 PNG: 50 million fewer
instructions per capture (gradient -4.3%, noise -1.4%); gradient
median 69.0 -> 65.9 ms.
2026-11-07 18:37:16 +01:00
LibretroAdmin 6d7386a90c rdeflate: Huffman symbols sorted by radix, not insertion, for big alphabets
Each dynamic block builds its codes by first sorting the used symbols
by frequency.  That was an insertion sort, which is cheap for a few
symbols but costs about m*m/4 moves for a full alphabet - and a noisy
block, which every busy screenshot has plenty of, uses all 256
literals, so the sort ran to some 16000 moves per block.

Alphabets of more than 48 symbols now go through a stable byte-wise
radix sort on the frequency: one pass for each byte the largest
frequency uses, which is one or two since a block holds at most
RD_BLOCK_SYMS symbols.  The symbols come in in symbol order, so a
stable sort gives the same order the insertion sort did - ties still
fall in symbol order - and the codes and the stream are unchanged.
Small alphabets (distances, code lengths, short blocks) keep the
insertion sort.  The ping-pong half uses gl_wt and gl_left, which are
free until the tree build; nothing new is allocated.

Compressed output is byte-identical to before over the level 0-9 x
input chunking x six-input matrix, zlib-wrapped and raw; the sort
itself matches the insertion sort on 200000 random arrays of 49-288
symbols with frequencies up to 32 bits (ASan/UBSan clean); the rzip,
rpng, archive and encodings sample tests pass; builds C89 without new
warnings and for aarch64.

screenshot_io_bench, 3840x2160 PNG: noise instructions -5.8%
(11.17 G -> 10.52 G), best 427 ms -> 392 ms; gradient unchanged.
2026-11-07 18:29:53 +01:00
LibretroAdmin dc8751ca88 rdeflate: stored blocks copy their bytes in runs, not one at a time
When a block does not compress - noise, video frames, already-
compressed archive members, the busy parts of a screenshot - it goes
out stored, and every byte of it was moved by a loop that tested for
output room, copied one byte and decremented a counter.  On a noisy
4K screenshot that loop was a tenth of the whole encode.

The bytes now go out with memcpy, in runs as long as the remaining
block or the output room allows; a full output still suspends the
copy exactly where the byte loop did, so a caller handing in small
output buffers sees the same stream.

Compressed output is byte-identical to before: the earlier level 0-9
x chunking x six-input matrix (zlib-wrapped and raw, 1 MB output),
and a focused stored-block run - noise, half-noise and 4-bit noise at
levels 1, 6 and 9 into output buffers of 7, 8, 16, 17, 100, 4096,
65537 bytes and 1 MB, all suspending mid-block; ASan/UBSan clean; the
rzip, rpng, archive_zip_methods, archive_stream and encodings sample
tests pass; builds C89 without new warnings and for aarch64.

screenshot_io_bench, 3840x2160 PNG, noise: instructions -9.2%,
best 451.9 ms -> 403.7 ms, median 556.3 -> 492.3 ms; gradient (no
stored blocks) unchanged.
2026-11-07 18:19:10 +01:00
LibretroAdmin a5c01e4ea0 audio: the resume ramp's flag is handed over, not raced
On the threaded pipeline audio_driver_pause_fade() armed the resume
ramp by writing fade_in_pending, a plain bool, on the main thread, and
the producer read and cleared it in audio_driver_submit_width() - on
the thread that submits, which for a core delivering from a thread of
its own is not the main thread. A data race; a resume could also lose
its ramp if the clear landed after a new arm.

fade_in_pending is now a retro_atomic_int_t. The main thread stores it;
whoever applies the ramp takes it with fetch-and against zero
(audio_driver_fade_take(); exchange is not on every retro_atomic.h
backend), in the producer and in audio_driver_arm_resume(). No lock,
and nothing waits.

ff_speedup gains a lane with the producer on a thread of its own while
the main thread ends frames, holds and releases fast-forward and
pauses, the consumer's share taken on the main thread. Under
ThreadSanitizer, which CI already runs ff_speedup with, it reported
this race and nothing else after the fast-forward measurement fix
(e24dafdc), and reports none now. The harness clock can run on real
time for a case with threads.
2026-11-07 18:18:41 +01:00
LibretroAdmin 51028e640e pipeline_clocked: one setting per lane, the CI step in 12 s
The CI step runs its lanes at once, in real time, so it takes as long
as the longest lane: rate_control, four settings at each of two device
latencies, 24 s; ff_exit, five transitions, 21 s; setpoint, three
latencies twice over, 18 s.

setpoint and rate_control now take a device latency (setpoint_16,
_32, _64; rate_control_32, _64) and ff_exit a transition (ff_exit_1
to _5), as audio_paced already did, and the step runs all fourteen at
once. The longest is now a single setting: 12 s of wall time for the
step here, three runs, no failures and no retries, on two CPUs. The
whole-case names still run the whole case, and a name that matches
nothing fails instead of passing on no run.
2026-11-07 18:18:38 +01:00
LibretroAdmin a840c07266 audio: the s16 gain and the synth sum carry the audio, not a copy
Three copies on the flush path that the next pass overwrote:

- Integer front pair at unity to an s16 device with a master gain:
  the batch was copied into the output scratch, then the gain ran over
  it in place. The gain now reads the batch and writes the scratch, one
  pass (audio_driver_gain_s16(), the same Q16 multiply).
- In-process MIDI synth on the integer path: the game audio was copied
  into the writable view, then the synth summed into it. The sum is now
  written from the game audio, one pass, and a synth that rendered
  nothing leaves the batch where it is, uncopied.
- In-process MIDI synth for a float core: copied in, then summed. The
  sum is written in the pass that brings the audio in. The synth renders
  before the path is chosen, so one that has nothing this batch no
  longer forces an s16 core off its passthrough through two conversions.

Output is unchanged sample for sample. Measured on 800-frame stereo
batches: the float synth sum 167 -> 132 ns per batch; the s16 gain
saves the 23 ns copy, about 0.5% of that pass; a silent synth saves the
copy and, on the float path, an s16 -> float -> s16 round trip.

unity_identity gains lanes: the integer gain at an attenuation and at a
saturating boost against the Q16 oracle; the synth sum on the integer
pair and for a float core against what the copy and the add gave; and
a silent synth leaving the batch on its passthrough, which on the float
path failed before (one s16 -> float and one float -> s16 conversion).
The shared stubs' synth is replaced there by one the cases control.
2026-11-07 17:52:40 +01:00
LibretroAdmin e24dafdc4d audio: the fast-forward measurement has one writer
The fast-forward speed measurement (last_flush_time and its two
averages) is taken by one thread: the submitter on the inline
pipeline, the main thread's frame end on the threaded pipeline and for
a callback core. Other threads wrote it too. Inline, the main thread's
frame end, pause and stop reset it while a core submitting from a
thread of its own was measuring. A callback core's audio thread reset
it beside the main thread's frame-end measurement. On the threaded
pipeline the producer read and reset it, and pipe_ff_frames was a
plain size_t added on the submitting thread and zeroed on the main one.
These are data races, and on 32-bit targets a 64-bit store can tear.

Now only the measuring thread writes the measurement. Any other thread
asks: one flag, stored only when not already up, which the measuring
thread takes before it next measures (audio_driver_ff_mult_sync(),
taken with fetch-and since exchange is not on every retro_atomic.h
backend). An idle frame costs one load. pipe_ff_frames is atomic: added
by the submitter, taken by what is there at the frame end, never stored
to zero. The producer knows the first frame of a hold from a flag of its
own instead of reading the main thread's measurement. A flush outside
fast-forward on the measuring thread still resets outright, which the
inline transport's source stamp overwrites as before.

This replaces the request-handler resets added for pause_fade in
0e35c138 with the same ask every other thread uses.

discrete_multichannel gains a lane with a core submitting from a
thread of its own while the main thread ends frames, holds and releases
fast-forward and pauses. Under ThreadSanitizer it reported nine races
on last_flush_time and pipe_ff_frames on the old code and none now; the
CI step builds it with TSan and runs that lane. Checks that read the
measurement straight after another thread's reset now accept the
pending ask.
2026-11-07 17:52:36 +01:00
LibretroAdmin 3b86a2126d pipeline_wakeups: the fast-forward stall lane in 2.3 s, not 3
check-ff-stall runs in real time, twice, and again under
ThreadSanitizer in CI. Each run spent 0.5 s settling into the hold,
1.5 s stalled and 1 s after it.

The stall is now 1.25 s, the shortest that still takes the old code's
estimate past the stretch's 4x duration bound with margin (from a bit
over 1 s); at 0.5 s the old code passes. A quarter of a second settles
on each side and half a second is judged, as before.

The lane still fails on the code before the gap fix (the stretch off
60 of 60 frames) and passes with it, three runs each variant, and under
ThreadSanitizer.
2026-11-07 17:09:02 +01:00
LibretroAdmin 0e35c13878 audio: reset the inline flush measurement where it is taken
audio_driver_pause_fade() reset the fast-forward flush measurement on
the main thread. That is right where the measurement is taken there:
the threaded pipeline's producer and a callback core measure at the
frame end. Inline, the thread that submits measures, and a core may
submit from a thread of its own; the main thread's reset then wrote
last_flush_time beside that thread's read-modify-write of it, a 64-bit
store that can tear on 32-bit targets.

Inline, the reset now rides the pause and resume requests pause_fade
already posts, applied by the submitter under the inline claim before
its next flush measures. The main thread resets only for the threaded
pipeline and callback cores, and always without HAVE_THREADS.

The ff_speedup and discrete_multichannel pause lanes pass unchanged.
2026-11-07 17:09:02 +01:00
davey 4635151a1d audio: seed the fast-forward estimate again after a stall
A stall inside a fast-forward hold was measured as one long flush
interval, and a long enough one pushed the speed estimate past the time
stretch's duration range. The stretch then stayed off for the rest of
the hold: the threaded transport suspends and resumes only from a
drained ring, which fast-forward keeps full; and with Speedup off the
inline flush put back the stamp from before the interval, so every
later flush measured from it.

An interval longer than the average's whole window at 1x
(AUDIO_FF_EXP_AVG_SAMPLES expected intervals) is now a gap, not a speed:
the estimate seeds again at the configured ratio, as after a pause.
Shorter intervals are still measured. One of them can take the estimate
of a hold running at 1x or faster down to about half speed at most,
inside the stretch's range, and any single interval that could push it
out is a gap. The inline, threaded and callback-core paths share the
measurement.

Inline, the stamp is put back only for the Speedup fallback, which may
measure the interval again. Put back otherwise, it stays stale until it
ages into a gap.

The ff_speedup and discrete_multichannel harnesses stall a 2x hold for
three seconds, threaded with and without Speedup, and inline. Inline, a
slow spell of intervals under a gap also takes the estimate out of
range, and the stretch has to be back within a few frames of the hold
returning to 2x. pipeline_wakeups gains a lane on the real wrapper
without Audio Sync, where the consumer primes again after the stall; it
checks that the hold's audio and the stretch come back. The native
transport CI job runs it, also under ThreadSanitizer.
2026-11-07 17:09:02 +01:00
davey 11b38de1bf audio: don't measure a pause as a fast-forward flush interval
A pause inside a fast-forward hold left the flush timestamp from before
it, so the first flush after the resume measured the whole pause as one
interval. The speed estimate then read the core several times slower
than real time, outside the time stretch's duration range, and the
stretch stayed off until fast-forward was released; inline, only with
Speedup off.

Reset the measurement in audio_driver_pause_fade(), on the main thread
as the frame end resets it. Every pause and resume goes through it, and
so do the jump fades of a state load or core reset, where the reseed is
harmless. The next flush seeds the estimate at the configured ratio, as
the start of a hold does.

The ff_speedup and discrete_multichannel harnesses pause a 2x hold for
125 ms, threaded and inline, and check that the speed estimate and the
stretch hold through it.
2026-11-07 17:09:02 +01:00
LibretroAdmin 2443ac01fe menu: the media viewer's title follows the image shown
In Ozone, opening an image from the file browser with OK and pressing
Up/Down to step to the next image changed the picture but kept the
first image's name as the title.

The title was refreshed by each menu driver on a selection change, and
Ozone only did that when the fullscreen view had been asked for with
START. The viewer opened by OK sets the "shown" flag but not the
"wanted" one, so its title was never refreshed while stepping.

Every step through the viewer - the OK that opens it, Up/Down/Left/
Right, continuous playback - goes through
menu_media_open_selected_file, so the title now belongs there: it takes
the driver's title buffer and rewrites it for the file it opens. Ozone
and XMB pass theirs; no driver can open a file in the viewer and leave
the old name up. A row the viewer cannot show leaves the title alone.

samples/gfx/media_controls: viewer_title drives the real
menu_media_browser.c through OK, Down, a row the viewer cannot show,
Down past it and Up, checking the title each time, plus truncation to
the buffer and no buffer. It fails on the old code.
2026-11-07 16:29:11 +01:00
LibretroAdmin 1838427ffe rjpeg: decode Adobe CMYK and RGB JPEGs - any component ids, RGB colour
The frame header refused any JPEG whose component ids were not 1, 2,
3(, 4) or 0, 1, 2(, 3).  Adobe's 4-component files - the CMYK and
YCCK the output path has a whole branch for - number their components
'C', 'M', 'Y', 'K', so every one of them failed to load: Photoshop
CMYK JPEGs, and Pillow's or libjpeg's CMYK output.  libjpeg takes any
ids; the scans name components by id anyway.  Now any ids are taken,
and only two components sharing one is refused.

That also lets through the 3-component files that hold R, G and B
rather than Y, Cb and Cr, which the same check used to refuse (their
ids are 'R', 'G', 'B') and which would otherwise come out with a
YCbCr transform applied to RGB.  The colour space is decided as
libjpeg decides it: a JFIF APP0 means YCbCr; else an Adobe APP14's
transform 0 means RGB; else ids 'R', 'G', 'B' do.  RGB rows skip the
colour transform and the fused YCbCr kernel and are only put in the
requested byte order.  The JFIF segment is now recognised for this.

Decoded pixels are unchanged for every YCbCr and greyscale image in
the 176-file Pillow and 24-file cjpeg corpus (baseline and
progressive, all samplings, restart intervals), through the iterative
decode in both byte orders and the monolithic decode.  The 66 Pillow
CMYK cases (baseline and progressive, 1x1 to 1920x1080), which all
failed before, now decode and match libjpeg's CMYK samples (C*K/255
etc. on the Adobe-inverted values) within one level; cjpeg -rgb
output (Adobe transform 0), its progressive form and the same file
with every APP segment stripped (ids alone) match libjpeg within one
level, where before they failed.  Of 300 corrupted progressive files,
two that only failed for a mangled component id now decode, as they
do in libjpeg; ASan/UBSan and LeakSanitizer clean over 500 corrupted
files; the rjpeg and image_loader sample tests pass; builds C89, as
C++ and for aarch64.
2026-11-07 15:57:24 +01:00
LibretroAdmin a37e64431c rjpeg: progressive finish emits as it goes, through ring-sized planes
A progressive JPEG decoded iteratively kept a full-size plane per
component alongside its coefficients, inverse-transformed every
component into its plane one after another, and only then - in
rjpeg_process_image - ran the resample and colour conversion over
the whole image, reading the planes back cold.  At 4K 4:4:4 that is
24 MB of planes nobody needed whole and one more trip through DRAM.

The finish phase now goes one MCU row at a time across all
components: dequantise and inverse-transform that row's blocks, then
emit the output rows that are complete - one MCU row behind, exactly
as the baseline fusion does, since the vertical resampler's far row
for the tail of a span is the first plane row of the next.  The fused
emitter is armed when the last scan ends; the planes, which the scans
never touch, are allocated ring-sized (three MCU-row spans) on the
iterative path, as baseline's already are, and rjpeg_process_image
finds the output made.  The monolithic path keeps whole planes and its
own finish, unchanged.

Decoded pixels are bit-identical to before on 200 images - baseline
and progressive, 4:4:4, 4:2:2, 4:2:0 and cjpeg's 2x1, 1x2, 4x1, 4x2
and 1x4 sampling, restart intervals (every 3 MCU rows, every block),
an optimised progressive script, a jpegtran-progressive transcode,
greyscale, sizes 1x1 to 1920x1080 - through the iterative decode in
both byte orders and the monolithic decode, and on 300 corrupted or
truncated progressive files; ASan/UBSan and LeakSanitizer clean; the
rjpeg and image_loader sample tests pass; builds C89, as C++ and for
aarch64.

Progressive, iterative decode:
  3840x2160 4:4:4  peak heap 110.8 MB -> 86.2 MB, LL misses -22%,
                   best 193.7 ms -> 178.5 ms
  3840x2160 4:2:0  peak heap  72.5 MB -> 60.3 MB, LL misses -17%,
                   best 123.1 ms -> 116.4 ms
  1920x1080 4:2:0  peak heap  18.2 MB -> 15.2 MB
2026-11-07 15:56:38 +01:00
LibretroAdmin cf774a522e rvp8: save the entropy a frame's own updates fall back to in one place
GCC 15 warned that sv_ym may be used uninitialized in
rvp8_video_decode: the saved sets were filled under !kf and read back
in the else of a separate kf test, a pairing it does not follow. The
save now fills them for every frame - a key frame with the defaults it
used to reset to at the restore - and the restore copies them back
whenever refresh_entropy_probs is 0. The same entropy results, and
the warning is gone at -O2 and -O3.

Error-resilient (refresh_entropy_probs 0, a key frame every 20) and
normal 176x144 libvpx streams decode byte-exact against FFmpeg before
and after.
2026-11-07 15:55:09 +01:00
LibretroAdmin 0788b7e002 rdeflate: insert a match's interior positions in one tight loop
Every position a match covers goes into the hash chains so later
matches can reach it, and on the repetitive content screenshots are
made of - flat runs, gradients, upscaled pixel art - that is nearly
every byte.  Each one was a call to rd_insert that re-chose the hash
(a run-time CPU-feature branch on a stock x86-64 build), loaded its
three bytes afresh, and was guarded by a per-position bounds test:
42% of a 4K screenshot's encode was spent there.

rd_insert_range() now does a whole interior at once: the bound is
worked out once (rd_insert_end, the same q + 3 <= end), the hash is
chosen once per run, the tables stay in registers, and the shift-xor
hash rolls from one position to the next - ((h << 5) ^ next) & mask is
exactly the hash of the next position, since what the mask drops from
h would be shifted out past it anyway.  The chains it builds are the
ones the calls built, position for position, so the output does not
change.  rd_insert itself, still used for the position being matched,
is inline.

Compressed output is byte-identical to before for levels 0-9, zlib-
wrapped and raw, fed 1, 7, 4096, 65536 bytes at a time or all at once,
on gradient, noise, flat-run, text, zero and mixed inputs - through the
run-time CRC32 hash, the shift-xor hash (CPU features masked) and the
compile-time SSE4.2 hash; ASan/UBSan clean on both run-time paths; the
deflate, rzip, archive_stream, archive_zip_methods, rpng and other
encodings sample tests pass; builds C89 without new warnings and for
aarch64 with and without +crc.

screenshot_io_bench, 3840x2160 PNG:
  gradient  instructions -25.6%, best 75.2 ms -> 58.8 ms
  noise     instructions  -4.1%, time unchanged within noise
2026-11-07 15:49:53 +01:00
libretroadmin 05a5f1ca4a rslc: what a Wii U emulator's shaders take; a division by zero folds as IEEE
RSLC_EXTENDED takes the rest of the samplers - sampler1D, sampler3D,
samplerCube and samplerCubeArray of each base, and the 1D, 2D, 2D array,
cube and cube array shadow samplers - with every texture function GLSL
has on them, the shadow ones through the Dref sample and gather
instructions. It takes a stage redeclaring its gl_PerVertex block or
gl_FragCoord, a geometry stage's inputs declared as plain unsized
arrays, gl_PointCoord, gl_PrimitiveIDIn, and GL_EXT_spirv_intrinsics'
spirv_execution_mode with its capabilities and extensions; a float
controls mode declared without them gets the ones it needs. These go to
SPIR-V only; the other destinations refuse a stage that uses them.

A constant division by zero folded to zero; it folds to the infinity or
NaN IEEE gives, as glslang folds it. Cemu's decompiled shaders and the
community graphic packs written for them compile to SPIR-V that draws as
glslang's does, pixel for pixel on lavapipe.
2026-11-07 15:39:41 +01:00
libretroadmin 7e5c815578 formats/zar: lookups bisect, directories found and listed with places, a writer
A lookup finds directories as well as files, the root at the empty
path, and searches each directory's sorted entries by bisection instead
of scanning them. A listing's entries carry their place: a file's
offset and size, a directory's first entry and count. zar_index_offset()
tells a caller where the index begins, so it can keep that part of the
file in memory and answer lookups and listings without I/O.

zar_write.c builds an archive front to back through a write callback,
compressing blocks with rzstd where HAVE_RZSTD is defined and finishing
with the footer's SHA-256, so an emulator can make .zar/.wua files
without the reference ZArchive library. Its output reads back with the
reference reader, and the reference writer's output with this one.
2026-11-07 15:39:37 +01:00
LibretroAdmin 4a0d8c8c92 image_texture_scale: the box stage reduces the image in its own buffer
Capping an oversized image box-averaged it by a power of two into a
second buffer of the reduced size, then either kept that (when it was
the target) or ran the sinc stage from it, freeing the original in
both cases.  A 4K screenshot capped to 1920 held an 8.3 MB copy it did
not need: every averaged pixel lands below every source pixel a later
block still reads (the next block starts at y*f*sw + (x+1)*f, the store
is at y*dw + x, with f >= 2), so the reduction can be made in place.

Now it is.  When the box result is the target the buffer is shrunk to
fit with realloc and kept - no allocation at all - except on GEKKO,
whose decoders memalign their output.  When the sinc stage follows,
its output buffer and filter are made before the box runs, so a
failure still leaves the image exactly as it was; the box stage itself
cannot fail once its geometry has been checked.

Two things made the in-place loop slower until handled: the compiler
must assume the output aliases the source it is still reading, so a
row's averages are built in a 64-wide local run and copied out; and
the box being a power of two, the four divisions per pixel become
shifts, which give the same result for unsigned sums.

Output is bit-identical to before - pixels, dimensions and return value
- over 352 cases: 11 shapes from 2047x2 to 16385x3, eight caps, 8- and
10-bit, with and without the upscale stage; ASan/UBSan clean, no
leaks.  The image_scale_transfer test now asserts the new ownership:
an exact box makes no allocation (it runs with an allocation budget of
0), a box + sinc makes one, and a failed sinc set-up leaves the source
untouched; image_loader passes too.  Builds C89, as C++, for aarch64
and with GEKKO.

4K -> 1920x1080: additional image storage 8.3 MB -> 0, 17.9 ms instead
of 20.1 ms (8-bit) and 14.5 instead of 17.2 (10-bit).
2026-11-07 15:22:15 +01:00
LibretroAdmin cc070e91a9 rpng: interlaced pass 7 straight into the image; one pass buffer
An Adam7 image allocated a buffer for each of its seven passes,
converted the pass into it and scattered it into the image, and freed
it - and freed it again and reallocated it every time a pass found
its data not yet inflated, which on a streamed decode is most passes
several times over.  Each pass also allocated and freed its own three
scanline buffers.

Now:
- pass 7 (x 0, stride 1) is the image's odd rows, whole, so it is
  unfiltered and converted straight into them: the output cursor
  steps two image rows a line (out_stride, which the regular path
  sets to the width and passes 1-6 to the pass width).  Half the
  image is no longer written into a buffer and copied out again.
- passes 1-6 share one buffer, allocated once at the largest of them
  and kept, through the waits too, until the decode ends.
- the three scanline buffers are one block, reserved once at the
  image's full pitch, which no pass exceeds, and kept across passes.
All of it goes back through the buffer pool where one is set (the
pool's three line slots become one).

Decoded pixels are bit-identical to before on a new corpus of 150
interlaced images - grey 1/2/4/8/16-bit, RGB and RGBA 8/16, grey+alpha,
palette 1/2/4/8-bit with tRNS, tRNS on grey and RGB, sizes from 1x1
up through every empty-pass shape, single and many IDATs - and match
Pillow on the 8-bit ones; on the 546-image corpus, its multi-IDAT
re-chunking, the APNG set and 550 corrupted or truncated files;
ASan/UBSan clean, no leaks; the rpng sample tests pass; builds C89,
as C++, as one translation unit, for aarch64 and with GEKKO.

800x600 RGBA interlaced, decoded twice: 196 allocations (88 MB) ->
26 (10.4 MB), instructions -1.9%, D1 and LL data misses -8%.
2026-11-07 15:11:42 +01:00
LibretroAdmin fabd4442a2 rwebp: an unpacked colour-indexing transform is applied in place
The VP8L colour-indexing (palette) transform always expanded into a
second full-size buffer, w*h words, and then freed the indexed one.
That buffer is only needed when the indices are packed - 2, 4 or 8 to
a pixel for palettes of 16 colours or fewer - where the expansion runs
top to bottom and would overwrite packed rows not yet read.  A palette
of 17 to 256 colours carries one index per pixel, so each index now
becomes its colour where it lies; the fused channel swap already
falls back to the stream's own buffer when there is no expansion
buffer.

Decoded pixels are bit-identical to before, and to libwebp's dwebp,
on palette images of 3, 12, 40, 100, 200 and 250 colours with and
without alpha (packed and unpacked), lossy images with palette-coded
alpha, a palette animation, and 160 corrupted palette files plus the
earlier corrupted sets; ASan/UBSan and LeakSanitizer clean; builds
C89, as C++ and for aarch64.

640x480 with a 40-colour palette: 1.2 MB less allocated,
instructions -7.7%, D1 misses -19%.
2026-11-07 14:53:47 +01:00
LibretroAdmin 80e939456d trans_stream/rpng: an APNG stream reuses one inflater across frames
Each APNG frame still made a new inflate stream and freed it at the
end of the frame: for the built-in inflater a calloc of 73 KB - its
32 KB back-reference window and Huffman tables zeroed afresh - and for
zlib an inflateInit2 with its window allocation.  On small frames that
zeroing cost more than decoding the frame.

trans_stream_backend gains an optional last member, reset: put a
stream back to the state stream_new gave it, keeping its allocations
and whatever define() set.  The built-in inflater implements it with
rinflate_reset, which already restores a fresh instance without
re-zeroing the window or tables (a stale window byte is unreachable
once wprior is 0, and tables are rebuilt before use); zlib with
inflateReset, which keeps the window inflateInit2 allocated.  Every
other backend lists NULL, and a backend that does not mention the
member gets NULL too.

rpng's buffer pool gains a slot for the stream.  A decode takes it -
reset as it is taken - when its backend matches and can reset, and
otherwise makes one as always; a finished, failed or abandoned decode
gives it back, or frees it where there is no pool or no reset.  So an
APNG stream inflates every frame with the one inflater, and stills
and every other trans_stream user are untouched.

Composited frames are bit-identical to before on the whole APNG set
and 250 corrupted or truncated files, with the built-in inflater and
with zlib; still PNGs decode identically on the 546-image corpus;
ASan/UBSan clean and no leaks under either backend; the rpng,
archive_file, archive_stream, archive_zip_methods and archive_zst
sample tests pass; builds C89 without new warnings, as one translation
unit and for aarch64.

Two passes of an animation, instructions / data refs / allocated:
  128x128, 25 frames        -50.6% / -69.5% / 4.1 MB -> 0.54 MB
  77x51 Adam7, 10 frames    -31.9% / -60.3% / 1.8 MB -> 0.38 MB
  640x480, 20 frames         -1.3% /  -4.1% / 20.3 MB -> 17.5 MB
2026-11-07 14:51:11 +01:00
LibretroAdmin abcd6c2f64 rpng/rpng_apng: an APNG stream reuses its decode buffers frame to frame
Every frame of a playing APNG allocated, and freed again afterwards,
the decoder's inflate ring (at least 64 KB), its three scanline
buffers, an Adam7 pass buffer per pass for interlaced frames, and the
frame's whole decoded image - mid-playback, every frame, every loop.

rpng now takes an optional buffer pool (rpng_internal.h): a slot per
role - ring, the three lines, the pass buffer, the output image - each
holding at most one buffer, the larger one when two meet.  Every place
rpng allocated one of these takes it from the pool first and falls
back to the allocation it always made, and every place it freed one
gives it back instead; with no pool, giving is free(), so stills and
every other caller behave exactly as before.  The output keeps its
32-byte memalign on GEKKO, a pooled image having been allocated there
too.  The APNG stream owns a pool, hands each frame's image back once
composited, and releases it on close: once a stream has seen each size
it needs, frames stop allocating these.

Composited frames are bit-identical to before on every APNG in the
set (all colour types and depths, interlaced, palette with tRNS,
hidden default image, multi-fdAT frames, every dispose/blend mix) and
on 250 corrupted or truncated files; still PNGs decode identically on
the 546-image corpus, single and multi-IDAT; ASan/UBSan clean and no
leaks; the rpng sample tests pass; builds C89, as C++, as one
translation unit (griffin), for aarch64, and with GEKKO defined.

Heap allocations for two passes of an animation:
  128x128, 25 frames            534 -> 294
  77x51 interlaced, 10 frames   704 -> 412
  640x480, 20 frames            470 -> 372, 5.6 MB less allocated
What remains per frame is the decoder's own state: the rpng and
process structs, the inflate stream and the IDAT span list.
2026-11-07 14:38:28 +01:00
LibretroAdmin bafe97e650 rpng_apng: a frame's data is decoded where it lies, not copied
Every APNG frame was decoded by building a standalone PNG in the
stream's scratch: the file's IHDR (with the frame's size), PLTE and
tRNS, then all of the frame's IDAT/fdAT payloads copied end to end
into one IDAT - the whole compressed frame, every frame, every loop.

The scratch PNG now carries an empty IDAT where the data would go, and
the payloads are handed to rpng as spans of the animation file itself
(rpng_add_idat_spans, internal through rpng_internal.h), read in place
exactly as a still PNG's IDAT chunks are.  rpng keeps the base those
spans are relative to - buff_start unless a caller gives its own - and
refuses to mix the two.  The APNG part list is the shared span type,
and the duplicate PNG signature goes in favour of rpng_internal.h's.
With the previous change a frame split over several fdAT chunks costs
no more at the seams than one in a single chunk.

Composited frames are bit-identical to before on RGBA, RGB and RGBA at
16 bits, grey and grey 16 interlaced, grey+alpha, palette with tRNS
(interlaced and not), Adam7 RGBA, a hidden default image, frames split
over up to five fdAT chunks, every dispose/blend mix, the earlier ten
animations, and 250 corrupted or truncated files (same frames, same
failures); ASan/UBSan clean; the rpng sample tests pass; builds C89, as
C++, as one translation unit (griffin) and for aarch64.

A 640x480 20-frame animation: instructions -1.4%, data refs -8.2%,
D1 misses -4.7%, 165 KB less allocated.  Small frames gain little: the
decoder is still set up afresh per frame, which is the next step.
2026-11-07 14:27:39 +01:00
LibretroAdmin a2b0096bdf rpng: carry on into the same output across IDAT chunks
The compressed stream is fed one IDAT payload at a time.  When a
chunk's payload ran out partway through an output slice, the step
returned, and the next one rebound the output to a fresh slice before
handing over the next chunk.  To the inflater that rebind turns
everything produced so far into history, so the back-references that
reached across the seam were then served out of its 32 KB window a
byte at a time instead of copied within the buffer, and the window
was committed at every chunk end as well.

libpng and most other writers split the image data into 8 KB IDAT
chunks, so this hit nearly every PNG in the wild.  Now, when a chunk
runs out with room left in the slice and another chunk follows, the
next payload is handed over and the inflate carries on into the same
output in the same step.  Pacing is unchanged: a step still produces
at most one slice.

Decoded pixels are identical to before on the 546-image corpus (every
colour type and depth, interlaced and not, every filter) and on the
same corpus re-chunked into IDATs of 1, 7, 13, 100, 1000 and 8192
bytes with an empty IDAT mid-stream, which also decode identically to
the single-chunk originals; with the built-in inflater and with zlib;
ASan/UBSan and LeakSanitizer clean; the rpng sample tests pass.

A 3.1 MB RGBA PNG written with 8 KB IDATs now decodes at the cost of
the same image in one IDAT: instructions 664.3M -> 626.1M (-5.7%),
data refs -12.2%.  Single-IDAT files are unchanged.
2026-11-07 14:27:35 +01:00
LibretroAdmin 0b7c12251d Scanner: Wii U discs, WUX images and Wii U archives
Wii U content did not scan. It now takes the serial lookup by its
product code:

- a WUD, or a Wii U disc by another name (.iso, probed only once the
  other detectors find nothing, so other discs read nothing more), by
  the "WUP-P-XXXX" its header starts with;
- a WUX, through the new formats/wux, the same octets of the disc it
  stands for;
- a Wii U archive (.wua), by the product and company codes of its base
  title's meta/meta.xml, read through formats/zar.

formats/wux reads WUX, the Wii U image format that keeps each distinct
sector once, as the WUD it stands for. Like the other readers it does
no file I/O: it asks for each range it needs. Opening reads the
32-octet header alone; the sector index is read a window at a time,
only where a read needs it; a read's data lands in its own destination,
one request per run of sectors the file keeps in order.

formats/zar gains a directory listing, zar_dir_begin() and
zar_dir_step(), each name read whole, the listing's end ZAR_END.

A Wii U release's serial in the database is the product code and the
maker's two characters, or the code alone for a title sold as a
download; a disc does not say its maker. The code is therefore looked
up as a stem, in the Wii U database alone - glob() in a libretrodb
query now matches binary fields, as a serial is kept - and the release
picked is the one of the maker an archive names, else a download for
an archive and a disc release for a disc. The playlist names the
release found.

wux_test is new, zar_test lists directories, and the scan test gains a
Wii U lane: a WUD, a WUX, a disc named .iso, two archives whose update
is listed before the game, and another database holding the codes.
2026-11-07 13:49:21 +01:00
LibretroAdmin 5a076bcd12 rwebp: lossy alpha (ALPH) decoded straight into the frame, no plane
A VP8 image or animation frame with an ALPH chunk built a w*h byte
plane for its alpha - a memcpy of the whole chunk for raw alpha, a
narrowing pass out of the VP8L decode for compressed alpha - then
unfiltered that plane top to bottom in a second pass and merged it
into the decoded pixels in a third, and freed it.

alph_apply() now takes the frame and does it a row at a time: the
row's filtered bytes go into a two-row buffer (the row, and the one
above that the vertical and gradient filters predict from), are
unfiltered there, and merged into the top byte of each pixel while
the row is still in cache.  The w*h plane and its two cold passes are
gone; what remains is one 2*w byte buffer.  Every check that can fail
(header, short raw data, a VP8L decode that fails) is made before the
first row is written, so a bad chunk leaves the frame opaque exactly
as before.

Output is bit-identical to before on stills with raw and compressed
alpha under every filter (none, horizontal/fast, gradient/best),
quantised alpha, lossy alpha animations from img2webp and ffmpeg, and
200 corrupted files; alpha matches libwebp's dwebp byte for byte;
ASan/UBSan and LeakSanitizer clean; builds C89, as C++ and for
aarch64.  A 3655x2053 image with alpha allocates 7.5 MB less per
decode and takes 8-10% fewer data-cache misses; instructions are
unchanged.
2026-11-07 13:36:39 +01:00
LibretroAdmin 2879b667b2 rwebp: a full-canvas copied frame becomes the canvas instead of being copied
An animation frame that covers the whole canvas and is copied rather
than blended - no_blend set, or a source whose alpha bytes are all
0xFF - was decoded into a buffer of its own and then memcpy'd row by
row over the canvas, every pixel of it, before that buffer was freed.

Such a frame replaces every canvas pixel and its buffer is exactly
canvas-sized, so the two now trade places: the decoded frame becomes
the canvas and the old canvas is freed in its place.  Nothing the copy
would have overwritten (the key-frame clear, the previous frame's
dispose-to-background) is done first either.  The stream's contract
already makes the returned canvas valid only until the next call, and
the channel-order switch converts whichever buffer is the canvas.

Composited frames are bit-identical to before on full-canvas lossy,
lossless and no_blend-with-alpha animations, sub-rectangle ones from
ffmpeg, img2webp (lossless, mixed, lossy) and gif2webp (lossless and
lossy, with disposal), and on the 300 corrupted files; ASan/UBSan and
LeakSanitizer clean; aarch64 builds.  A 4 s 640x480 lossy animation
of full frames no longer copies 1.2 MB per frame: data-cache misses
-34%, data refs -0.7%, instructions -0.3% (decoding dominates).
2026-11-07 13:14:53 +01:00
LibretroAdmin 1b448b1965 rac3/audio_transfer: an AC-3 seek resets the decoder in place
Every AC-3 seek threw the decoder away and made a new one: a calloc of
the whole decoder (some 26 KB) and the 640 cos/sin evaluations of its
transform tables, only to land in the state the old one could be put
back into.

rac3_decoder_reset() now does that: it zeroes the stream state - the
overlap delay, coefficients, grouped-mantissa and E-AC-3 frame state,
scratch - and reseeds the dither, exactly as rac3_decoder_new leaves
them, while the tables and the caller's DRC setting stay.  The struct
puts those first so the reset is one memset from the first stream
field to the end.  The seek calls it instead of new + free, and can
no longer fail on an allocation.

Output is bit-identical to before for 1/0, 2/0 (AC-3 and E-AC-3),
3/0, 3/1, 4/0 and 5.1 streams, f32 and s16, reads of 1 to 10000
frames with six seeks each (to 0, to a block boundary, mid-block,
past the end); ASan/UBSan and LeakSanitizer clean; the rac3 and
audio_transfer_ac3 sample tests pass; builds C89, as C++ and for
aarch64.  500 seeks with a short read after each on a 5.1 stream:
490 allocations (13.3 MB) -> 7 (0.25 MB), instructions -1.1%.
2026-11-07 13:11:05 +01:00
LibretroAdmin 1acc68146f Scanner: pick an Xbox release by the regions its executable names
Redump gives one serial to every release of an Xbox or Xbox 360 title,
and to the extras discs that came with them, so a serial lookup found
the first record - "Halo Movies (Japan)" for a North American Halo.

formats/xdvdfs now reads the regions a title is for: the game region of
an XBE's certificate, and the region of an XEX's security info, each
only where the executable is long enough to hold it. The scanner reads
the region field of the records a serial matches when it has regions to
go by, and takes the first record whose regions overlap them; when none
does, the first record stands in, as before.

The duplicate DB_EXTRACT_* definitions in database_info.c go: the
header carries them, with DB_EXTRACT_REGION added.
2026-11-07 13:10:24 +01:00
LibretroAdmin b93738dd4a gfx_thumbnail: the viewer reuses the still the file browser shows
Pressing OK on an image in the file browser read and decoded the file
a second time even though its thumbnail was already on screen.

menu_media_open_selected_file only reuses a thumbnail when
gfx_thumbnail_has_file says it holds the file, and that answered yes
only for a live animation session. Every still (PNG, JPEG, BMP, TGA,
single-frame GIF/WEBP) has no session, and neither does an animation
the preview budget refuses - large ones land as a still - nor one that
finished its last loop. All of those were reset and loaded again.

Each thumbnail now records a 64-bit hash of the path it was requested
from (no per-thumbnail path buffer), cleared on reset.
gfx_thumbnail_has_file matches it while the still is shown, or while
its load is in flight and not superseded; a failed or cancelled load
still gets requested again.

samples/gfx/gfx_thumbnail_anim lane 16 drives the real
gfx_thumbnail.c: a still is recognised in flight and once shown, and
not for another path, after a reset, when superseded or when the load
failed. It fails on the old code.
2026-11-07 12:57:10 +01:00
LibretroAdmin 5c6b2a3867 rvp8: per-macroblock info kept across key frames
A key frame freed the per-macroblock info array, and the next inter
frame calloc'd it again, so every key frame of a stream playing cost
a free and an allocation of mbw * mbh entries (8160 at 1080p) in the
middle of playback.

The video decoder now owns the array for its life and grows it only
when a key frame brings more macroblocks.  A key frame still detaches
it from the decode state, and the first inter frame after one clears
the entries it uses, which is what the calloc gave it, so the decode
reads exactly what it did.  A failed begin leaves the array with the
decoder instead of freeing it.

Output is bit-identical to before on every stream in the VP8 set
(altref, error-resilient, partitions, odd sizes, 1080p, noisy), a
keyframe-every-7 stream, one switching between three frame sizes
mid-stream, and 120 corrupted streams; ASan/UBSan and LeakSanitizer
clean; builds C89, as C++ and for aarch64.  Heap allocations for the
4 s keyframe-every-7 clip: 23 -> 9.
2026-11-07 12:56:26 +01:00
LibretroAdmin 76502adbb2 rh264: unescape by stretches, parameter sets without an allocation
Turning a NAL into its RBSP moved every byte through a four-way test
for an emulation_prevention_three_byte, one byte at a time, though
such a byte turns up only a few times per megabyte.  The scan now
jumps from one 0x03 to the next with memchr, checks the neighbours of
each against the same rule on the same input bytes, and memcpys the
stretches between the ones that qualify.

SPS and PPS NALs were unescaped into a malloc of their own and freed
again.  Streams repeat their parameter sets in band, often before
every IDR, so that ran during playback.  They now unescape into the
current context's scratch, as slices already do; nothing of a slice is
in it between NALs, a queued slice having taken its buffer along.

The new unescape matches the old byte for byte on 3,000,000 random
buffers of 0 to 63 bytes drawn heavily from 0x00-0x03, under ASan with
exact-size buffers.  Decoded pictures are identical to before on
baseline, main with in-band parameter sets every 10 frames, high with
8 slices, B-frames, CAVLC at qp 0, a flat high-bitrate clip and 4:4:4
lossless; the lossless (threaded and not), state and hw sample tests
pass; ASan/UBSan clean; aarch64 builds.

Instructions to decode a whole clip:
  640x360 CAVLC qp 0, 6.9 MB   2831.5M -> 2750.8M (-2.9%)
  640x360 4:4:4 lossless       2582.6M -> 2560.6M (-0.9%)
  640x360 baseline crf 20       808.5M ->  802.8M (-0.7%)
Heap allocations, main with parameter sets every 10 frames: 100 -> 83.
2026-11-07 12:49:48 +01:00
LibretroAdmin 4aa4f063a4 rwebp: lossless Huffman tables from one block per image, not one each
Every Huffman table in a VP8L stream cost four heap operations: a
malloc and free of an 8 KiB symbol scratch and a calloc for the table
itself, then the same again for the code-length table read before it.
An image with a meta-Huffman image has five tables per group and can
have hundreds of groups, so a 3655x2053 lossless WebP made 770 heap
allocations to decode, nearly all of them this, and an animation paid
it again on every frame.

Now:
- the stream decode allocates the symbol scratch once, in the same
  block as its code-length scratch, and hands it to every table build;
- the code-length table (three-bit lengths, 7-bit root, so exactly 129
  entries and never a second level) lives on the stack, 516 bytes;
- every group's tables are carved in turn out of one arena per stream,
  sized at start for the five root tables of every group and doubled
  when a second-level table does not fit.  A move would invalidate the
  pointers, so each table records its offset and the pointers are set
  once the last group is read.  The arena is zeroed as it is handed
  out, exactly as the calloc'd tables were, so an incomplete code reads
  the same.

Heap allocations for a decode, old -> new:
  3655x2053 lossless           770 -> 23
  1024x768 lossless (-z 9)     304 -> 23
  640x480 lossless (-z 9)      132 -> 23
  10-frame 480x360 animation   896 -> 174
Total bytes allocated falls 8-24%; peak heap rises 0.3-1.6% with the
arena's slack.  Instruction counts and data-cache misses are unchanged
within noise: pixel decode dominates the time.

Output is bit-identical to before on lossless (-z 0/6/9, -exact with
alpha), near-lossless, lossy with lossless alpha, still and animated
(lossless and mixed) images, and on 300 corrupted files (same pixels,
same failures); ASan/UBSan and LeakSanitizer clean.  Builds C89
-pedantic warning-free, as C++, and for aarch64.
2026-11-07 12:43:26 +01:00
LibretroAdmin 642cb82fcc gfx_thumbnail_anim: stub dir_get_ptr
gfx_thumbnail_anim_test stopped linking when the savestate preview
started from the configured state folder: gfx_thumbnail.c now calls
dir_get_ptr(RARCH_DIR_SAVESTATE), which gfx_thumbnail_preview's stubs
already answer and this harness's did not. It answers with an empty
folder, as an unset one reads.
2026-11-07 12:39:07 +01:00
LibretroAdmin 2be0e71a84 audio_transfer: Opus packets decode straight into the caller's buffer
Every Opus packet was decoded into the context's pending buffer and
then copied into the caller's, whether or not the caller had room for
it as it stood.

A read with nothing pending now looks at the next packet's length in
its TOC byte and, when the whole packet fits in what is left of the
caller's buffer, decodes it there and counts it as produced; pending
is not touched.  Packets that cannot go out exactly as decoded still
take the pending path: one with pre-skip left to drop off its front,
any packet on a movie timeline (which maps and may overlap or pad
it), and the tail of a read too short for the next packet.  The end
bound (end granule, WebM padding) applies by count either way.  A
malformed packet whose TOC undercounts it is refused by ropus for
room before anything is decoded, and goes to pending as before, so
errors land where they always did.

Output is bit-identical to before for mono, stereo and 5.1 (mapping
family 1), 2.5, 20, 40, 60 and 120 ms packets, Ogg and WebM, s16 and
f32, reads of 1 to 10000 frames with and without a seek, and for 240
corrupted streams; ASan/UBSan clean, the audio_transfer sample tests
pass.

Decoding a whole stream, 4096-frame reads, instructions / data refs:
  stereo 20 ms  f32  -0.2% / -0.4%   s16  -0.1% / -0.2%
  5.1           f32  -1.4% / -8.8%   s16  -0.5% / -4.0%
2026-11-07 12:35:41 +01:00
LibretroAdmin f5eb373b59 raac/audio_transfer: AAC reads go straight from raac into the caller
The AAC arm decoded every access unit into a float buffer of its own
(raac interleaving and scaling its planar synthesis output into it),
then copied that buffer into the caller's on an f32 read, or, on an
s16 read, scaled each sample back up by 32768 and rounded it one at a
time.  Every sample was written twice, and s16 paid an extra scalar
conversion pass that raac already has in SIMD.

raac now offers the decode in two halves: raac_decode() leaves the
frame inside the decoder, planar, where synthesis put it, and
raac_copy_s16()/raac_copy_f32() interleave any slice of it into a
caller's buffer, converted exactly as raac_decode_s16/raac_decode_f32
would.  Those two entry points are now the decode plus a copy of the
whole frame, and the copies keep their SSE2/NEON paths at any offset
and length, with a scalar tail; mono f32 gets a vector path too.

The AAC arm decodes with raac_decode() and each read copies its slice
straight from raac into the caller's buffer, in whichever format it
asked for, so s16 and f32 still mix freely mid-packet.  The per-stream
float buffer, its allocation at start and its free are gone.

Output is bit-identical to before for mono, stereo, 3- and 6-channel,
ADTS and M4A, and the doubled-rate (16/22/24 kHz) SBR path, in f32,
s16 and alternating reads of 1 to 10000 frames with a seek, on SSE2
and the scalar build; ASan/UBSan clean, NEON and armv7 build, the
raac, audio_transfer and rmp4 sample tests pass.

Instructions to decode a whole stream, 4096-frame reads:
  44.1 kHz stereo M4A  s16 168.1M -> 136.4M (-18.8%)
                       f32 131.4M -> 130.7M  (-0.6%)
  22 kHz stereo (SBR)  s16 419.5M -> 389.8M  (-7.1%)
                       f32 391.6M -> 384.4M  (-1.8%)
2026-11-07 12:29:30 +01:00
LibretroAdmin d06c4b2ebd formats/zar: ZArchive reader; scan Xbox 360 games in .zar
formats/zar looks files up in a ZArchive and reads them, with no file
I/O of its own. A lookup asks for a directory's entries a run at a
time and for each name only as far as it can match; a read asks for a
block kept as it is straight into its destination, decodes a
compressed block (HAVE_RZSTD) into the destination when the read
wants all of it, and otherwise into one block kept for the reads that
follow.

The scanner takes a .zar by the title of the default.xex it holds.
2026-11-07 12:23:29 +01:00
LibretroAdmin 1481155965 rpng_apng: no CRCs for the PNG each frame is rebuilt as, no realloc
Each APNG frame is rebuilt as a PNG and handed to rpng, and three CRCs
were computed for it - over IHDR, IEND and the whole of the frame's
compressed data - though that PNG goes nowhere but rpng, which does
not check chunk CRCs.  They are left zero.

The region a DISPOSE_PREVIOUS frame saves was reallocated to that
frame's size on every such frame.  It is now allocated once, at the
canvas size every frame is checked to fit when the frames are indexed;
and if that allocation fails the region is not saved, rather than the
old buffer being kept at a size that may not match the frame restored
from it.

Four APNGs from FFmpeg and six variants rewritten to dispose to
previous (all frames, and alternating with background and none)
composite identically before and after, nine of them byte-exact
against FFmpeg; the tenth differs from FFmpeg before and after, where
a frame blended over a restored region shows the restored pixels as
the APNG specification has it and FFmpeg does not.
rpng_apng_bounds_test and rpng_decode_corpus_test pass.  0.7% fewer
instructions over a 20-frame animation.
2026-11-07 12:21:06 +01:00
LibretroAdmin 05a4c51ea6 logiqx_dat: game fields copied once, and decoded once
Each game's description, year and manufacturer went through a
PATH_MAX_LENGTH stack copy, a trim, five strstr scans and, for each
HTML code found, string_replace_substring's allocate, copy and copy
back, before a last copy into the field.  The replacing was also wrong:
rxml has already decoded the character references, so decoding
&amp;, &lt;, &gt;, &apos; and &quot; a second time turned a title's
literal "&amp;" (written &amp;amp; in the DAT) into "&".

The text is now trimmed by pointer, with the same whitespace rules as
before, and copied into the field once, bounded, as rxml hands it
over; the exported logiqx_dat_html_code_list table, which nothing else
uses, is gone with the loop.

Fields with entities, surrounding whitespace, over-long text, empty
text and UTF-8 read the same as before; only a doubly-escaped title
differs, and now reads correctly.  A 60000-game DAT reads identically
and takes 23% fewer instructions to load and walk.
scan_begin_budget_test passes.
2026-11-07 12:21:03 +01:00
LibretroAdmin e8dc09f986 natt: hand the UPnP reply to the XML document instead of copying it twice
Each of the three UPnP replies (device description, external address,
port mapping) was copied into a NUL-terminated heap buffer, then
copied again by rxml_load_document_string, which keeps its own.  The
buffer is now handed over with rxml_load_document_owned, which keeps it
as the document's or frees it on failure, so the second copy and its
allocation are gone and the caller no longer frees it.

natt_desc_parse_test passes.
2026-11-07 06:36:57 +01:00
LibretroAdmin 6af9ab068f rsubtitle_srt: no self-copy while normalising line endings
The normalise stage compacts the input as it turns CR and CRLF into
LF, copying every scalar from its read position to its write position
- onto itself, byte by byte, for the whole file, until the first CR,
which most files never have.  While nothing has been compacted the
scalar is already where it belongs, so both positions just advance;
copying starts at the first CR.  One scalar a work item, and its UTF-8
validation, as before.

The subtitle_srt tests pass.  Loading a 4.4 MB, 40000-cue SRT counts
3.6% fewer instructions in cachegrind; wall time on this box is within
its noise.
2026-11-07 06:32:58 +01:00
LibretroAdmin 3f3a7e2fa5 rvp8: no grey fill of every frame's planes
rvp8_begin filled the Y, U and V planes with 127 before every frame -
about 3 MB at 1080p - and the decode then overwrote all of it: every
macroblock writes its whole 16x16 (8x8 chroma) area, the 127 / 129
edges intra prediction reads beyond the frame are applied per
macroblock, and motion compensation clamps to the reference.  The fill
is gone.  WebP lossy, which decodes through rvp8_begin too, loses it as
well.

Seven libvpx streams - odd and tiny sizes (203x117, 17x9), noisy
content, alt-ref, error-resilient, four token partitions and 1080p -
decode byte-identical before and after, six of them byte-exact against
FFmpeg's libvpx decode (the error-resilient one already differed);
clean under ASan and UBSan, and Valgrind memcheck finds no use of an
uninitialised byte on five of them, so nothing read the fill.  On the
1080p stream cachegrind counts 4.8% fewer instructions, 25% fewer data
writes and 43% fewer L1 write misses; 6.42 -> 6.31 ms a frame.
2026-11-07 06:31:56 +01:00
LibretroAdmin 99fe08409e rvp8: a frame's probability updates are its own when it says so
A VP8 frame whose refresh_entropy_probs is 0 uses the probability
updates it carries for itself only; the next frame starts from what
this one started from.  rvp8_begin recorded the flag, and its comment
left the save and restore to the persistent decoder, which never did
either, so the updates carried on into every later frame.
Error-resilient streams send the flag on every frame, and they decoded
wrong from the first inter frame on.

rvp8_video_decode now keeps the context a frame inherits - coefficient,
MV and intra mode probabilities - before it reads its updates, and
when the frame turns out not to refresh, puts that back afterwards; a
key frame's starting point is the defaults, which are set again (the
mode and MV sets are refilled with theirs when found cleared, as the
next inter frame already does).

Four error-resilient libvpx streams - 352x288, a noisy 320x180, 640x360
with alt-ref and 97x55 with two token partitions - decoded wrong before
and are byte-exact against FFmpeg's libvpx decode now; six other
streams stay byte-exact; clean under ASan and UBSan.
2026-11-07 06:31:52 +01:00
LibretroAdmin 3c84217046 rmp3: stream frames decode in place, in and out
rmp3_stream moved every byte twice on the way in and once more on the
way out:

- Every input byte was copied into the 4 KB hold, and after each frame
  the hold's remaining ~3.6 KB was shifted down - about nine times the
  compressed stream moved, to reassemble frames that almost never
  straddle a window.
- Each decoded frame went into the stream's own PCM buffer and was
  copied out: 4.6 KB a frame for stereo s16, 9.2 KB for f32.

Now, with nothing held and the window holding as much as a full hold
would (or the rest of the stream once EOF is stated), the frame is
decoded where it lies, handed exactly the bytes the hold would have
been given, so the decoder sees nothing different; the hold is kept
only for a frame that straddles two windows, and a window that yields
no frame is left in the hold as before.  With room in the caller's
buffer for any frame the stream can carry - 1152 frames of up to two
channels, a channel change fixed up in place there - the frame is
decoded straight into it; the synthesis stores sample by sample, so any
alignment will do.  The first frame, which sets the stream's channel
count, and short outputs still go through the PCM buffer.

The output is bit-identical before and after on CBR and VBR, mono and
stereo, MPEG-1 and MPEG-2 and ID3-tagged streams, in s16 and f32, with
sliding windows of 700 bytes to the whole file and outputs of 100 to
5000 frames - 224 combinations; rmp3_frame_test passes; clean under
ASan and UBSan.  Decoding a 128 kbps stereo file, memcpy and memmove
drop out of the profile and L1 data misses fall by 25%.
2026-11-07 06:26:24 +01:00
LibretroAdmin 37121e88e9 formats/xdvdfs: Xbox 360 titles; scan Xbox 360 games
The title read takes the executable an Xbox 360 disc boots,
default.xex, ahead of an Xbox one's default.xbe, and reads one on its
own as well: an XEX's execution info, an XBE's certificate or an STFS
package's header, each only as far as the title needs, after a 24-octet
head that tells them apart. The game partition is also looked for where
development images keep it, and a padded root reads as empty.

The scanner takes .xex and .xcp files by their title, and Xbox 360
discs too; a disc of a set gets its place in it ("FO-2046-1"). A .xex
that is no Xbox executable, as an Atari 8-bit one, goes on to the CRC
lookup.
2026-11-07 06:23:32 +01:00
LibretroAdmin b903de9bfa audio_transfer: a resident FLAC keeps its last frame
A FLAC's last frame is shorter than its block size, so the decoder
runs out of input partway through the frame it was sized for.  rflac
reads that as the end of a span - roll back and wait for more - unless
rflac_set_eof says nothing more comes, and buffer mode, which hands the
decoder the whole file at once, never said so: the last frame was
waited for and lost.  A 3.7 s stream at 44.1 kHz read 159744 of its
163170 frames with 4096-frame blocks, short by up to a block on every
file whose length is not a whole number of blocks.

Buffer mode now calls rflac_set_eof with the span it hands over, and
so does the seek table's jump, which hands over the rest of the file.

With the end stated, rflac no longer copies its decoder (4.4 KB) before
every frame for a rewind point that cannot be used: an underrun after
rflac_set_eof is the end of the stream, kept rather than rolled back,
so the point is taken only while more input may come.

Eight FLACs - mono, stereo, 24-bit and 5.1, blocks of 192 to 16384 and
one without a seek table - now read every frame, byte-exact against
FFmpeg's decode; what was read before is an exact prefix of what is
read now; seeks land exactly; audio_transfer_flac_test passes; clean
under ASan.  Despite decoding the frames that were lost, cachegrind
counts 2.8% fewer instructions with 192-frame blocks and 0.8% with
1152.
2026-11-07 06:22:57 +01:00
LibretroAdmin b7717327d8 audio_transfer: FLAC seeks no longer overrun their stack scratch
A FLAC seek decodes forward from the nearest point it can reach into a
stack scratch of 1024 * 2 s16, asking for up to 1024 frames a step -
right for stereo, but a frame is a sample per channel, and FLAC goes up
to eight: a 5.1 stream wrote 6144 samples into 2048, past the end of
the stack buffer.  Any seek in a multichannel FLAC does it, including
the seek to 0 that loops a sound.

Each step now asks for at most what the scratch holds at the stream's
channel count, assuming the most (eight) before the header has been
read.  Both decode-forward loops, after the seek table's jump and after
a rewind, take the bound.

A 5.1 FLAC seeked at several points reads exactly what a straight
decode gives there, and ASan, which reported the overrun in
rflac_read_pcm_frames_s16 under audio_transfer_flac_seek_to, is clean;
mono and stereo streams read the same bytes as before.
2026-11-07 06:22:54 +01:00
LibretroAdmin 3b78dca6d7 rchd: decode a hunk a read covers whole straight into its buffer
Every hunk a read touched was decoded into the cache and its slice
copied out, so a read of whole hunks - every chdstream hunk load and
read-ahead, rchd_read_hunk_begin, and the inner hunks of a large byte
range - decoded each hunk and then copied all of it once more.  A
parent reference copied twice: the parent's cache, the child's cache,
then the destination.

A hunk the read covers whole and that is not already cached is now
decoded, or fetched from the parent, straight into the caller's
buffer, and the v3/v4 CRC is checked there.  The cache keeps holding
what it held, which is still a valid hunk; only a read of part of a
hunk decodes into the cache to copy its slice out.  No codec uses the
cache as scratch.

Every read pattern - all hunks forwards and backwards, whole-image,
unaligned, aligned multi-hunk, repeated and cross-boundary byte ranges
- gives the same bytes as before on chdman images in every codec
(none, zlib, lzma, huff, flac, zstd, a mix, and cdzl, cdlz, cdfl, cdzs
CDs with data and audio tracks) and on raw and CD children with
parents; byte ranges match the source image; children match libchdr
hunk for hunk; clean under ASan and UBSan.  Cachegrind counts 10%
fewer instructions over those reads on an uncompressed image, 5% on a
zlib one and 2.8% on a cdzl CD.
2026-11-07 06:16:20 +01:00
LibretroAdmin 3bd916da9f rchd: read a child's last hunk from its parent padding and all
A child CHD's parent reference is a whole hunk of the parent at a unit
position, fetched with rchd_read_begin.  When the image ends mid-hunk,
the child's last hunk then asks for bytes past the parent's logical
end, which a byte read rightly refuses, so reading that hunk failed
with RCHD_ERROR_PARAM - every read touching the last hunk of a child
image, the one a reader tries last.

When the reference starts on one of the parent's hunks it is that
whole hunk, padding included, which is what rchd_read_hunk_begin
gives; the fetch now uses it there and keeps the byte read otherwise.

A raw child CHD made by chdman, whose image is not a multiple of the
hunk size, now reads every hunk identical to libchdr reading the same
child and parent (hunk 150 of 151 failed before); a CD child still
matches on all 150, and byte ranges over the child match the source
image.
2026-11-07 06:13:26 +01:00
LibretroAdmin 839737e70f audio_transfer: decode AC-3 frames straight into the caller's buffer
Each frame went three times through memory: rac3 decoded it into the
arm's raw buffer, a pass mapped it into pend in the mixer's channel
shape, and the read copied pend out (or converted it to s16).  For
mono, 2.0, 3.0 and 5.1 the map is the identity - the decoder's slot
order is already WAV's - so the middle pass changed nothing at all.

- The map is marked identity when it is one.
- A read with room for a whole frame (an A/52 frame is at most 1536)
  decodes it straight into the caller's buffer: an identity map in f32
  directly, any other map or s16 from the raw buffer with the map,
  gain and conversion in one pass.  pend now only holds the frame a
  short read or a seek leaves part of.

Bit-identical before and after on seven layouts (mono, 2.0, 3.0, 2.1,
3.1, 4.0, 5.1) in f32 and s16 at request sizes 1 to 10000 frames with
a seek mid-stream; audio_transfer_ac3_test passes; clean under ASan
and UBSan.  Reading in 4096-frame requests: stereo 62.3 -> 60.3 ns a
frame in f32 and 65.8 -> 63.8 in s16, 5.1 118.8 -> 112.5 and 133.1 ->
123.7.
2026-11-07 06:09:25 +01:00
LibretroAdmin 117517be92 rvp9: no per-frame clear of the mode-info grid, MVs stored as decoded
Every frame cleared the whole mode-info grid - 54 bytes a cell, about
1.75 MB at 1080p - and once decoded read it all again to copy each
cell's references and motion vectors into the next frame's predictor
array.  Neither pass is needed:

- Every cell is covered by exactly one block, which writes its whole
  footprint (rvp9_decode_block's replicate loop) before anything later
  reads it, and the neighbours a block reads were decoded earlier in
  the frame, so the clear only ever wrote values that were
  overwritten.
- A block's references and MVs are final at the replicate loop, so
  the predictor entries are written there, in the same pass, and the
  end of the frame only swaps the two arrays.  Tile workers write
  disjoint cells, as they already do for the grid.

Byte-exact against FFmpeg/libvpx with 1 and 4 workers on
check_pixels.py's streams and five more: alt-ref with compound
prediction at 640x360 and 10-bit 512x288, a noisy 480x272, an
error-resilient 352x288 and a tiled 1080p one; clean under ASan and
UBSan.  On the 1080p stream cachegrind counts 2.3% fewer instructions,
13.5% fewer data writes and 14.6% fewer L1 data misses; wall time on
this two-CPU box moves within its own noise.
2026-11-07 06:04:59 +01:00
LibretroAdmin 8e9c2fb553 rinflate: leave the window alone once the stream has ended
Every process() call ended by copying its new output, up to 32 KB,
into the back-reference window, so the next call can resolve
references once the caller binds another buffer.  A stream that has
reached its end decodes nothing more until a reset, which clears the
window's reach, so that copy was never read: a one-shot inflate - each
CHD zlib hunk, a whole zip or 7z member - paid for a window copy of
its last 32 KB.  The commit is now skipped when process() returns END.

It cannot move to rinflate_set_out to cover the chunked case too:
rpng unfilters the inflate output in place before it rebinds, so a
late commit would capture the unfiltered bytes.

rinflate_window_test passes.  A CD hunk (18816 bytes, reset between
hunks as rchd does) inflates in about 57.9 us against 58.6 us.
2026-11-07 05:59:03 +01:00
LibretroAdmin 22c0b7f312 rpng: unfilter 8-bit RGBA rows straight into the output
With R,G,B,A byte order requested on a little-endian host, an 8-bit
RGBA row's conversion is the identity, so each row was unfiltered in
the inflate ring and then copied whole into the output.  Every
unfilter kernel reads the raw line and writes the line it is handed,
and their tails cover the out-of-place case, so the output row is now
that line and the row above it the prev: the copy is gone, the ring
releases each raw line as soon as it is read, and the wavefront pair
path writes its two rows into the output the same way.  Interlaced
images and every other format keep the ring path; the scalar build
copies the raw line into the output row and unfilters there.

546 synthetic images - RGBA8, RGB8 and gray+alpha, widths 1 to 1023,
each filter alone, runs of one filter for the pair path and random
mixes - decode identically before and after with RGBA order on and
off, clean under ASan and UBSan.  A 1080p RGBA8 image decodes in
27.5 ms against 27.8 ms; inflate is most of the rest.
2026-11-07 05:56:31 +01:00
LibretroAdmin 5ca1dee585 formats/xdvdfs: Xbox disc file system; scan Xbox games
formats/xdvdfs finds an Xbox disc's game partition, at the start of a
trimmed XISO or where XGD1, XGD2 and XGD3 images keep it, looks files
up in its directory trees and reads the title of its default.xbe, with
no file I/O of its own. A lookup asks for one entry at a time, only as
much of each name as decides the search.

The scanner takes .xiso files, and .iso and .chd files no other
detector finds a serial for, through the serial lookup by the XBE's
title ("MS-004"), without the ISO size hint, which would rule out
trimmed images. Other discs read nothing more for it.
2026-11-07 05:45:07 +01:00
LibretroAdmin 8c6c48f11a rwav: ADPCM block decoding with the predictor state in locals
Each ADPCM sample depends on the one before, so decoding is a chain of
dependent steps, and the decoder kept that chain's state in arrays
indexed by the runtime channel count: every sample's state went
through memory and back.  The blocks are now decoded with the state in
locals for a run at a time - an IMA channel's eight-sample run, or a
whole MS mono or stereo block - with MS at other counts still on
arrays:

- IMA, any channel count: per channel run, state loaded and stored
  once per eight samples.  Stereo decodes its two runs together, two
  independent chains the core overlaps.  The step, its product and the
  nibble's sign are one lookup in a 89x16 table built from the step
  table, which it replaces.
- MS mono and stereo: their own loops, both channels' state in locals.
- The nibble's sign is applied without a branch; the clamp keeps its
  branches, which real audio almost never takes, so they predict and
  cost nothing, where a select sits on the chain.
- A window stops decoding where it ends instead of running to the end
  of the block, and a run wholly inside it is stored without a test a
  sample.

Output is identical on every fixture, on the round-trip test and on
music-like and random payloads.  Decode time a sample at 2.1 GHz,
music-like IMA / MS, before -> after: mono 2.63 -> 2.58 / 5.15 ->
3.44 ns, stereo 2.44 -> 1.90 / 4.07 -> 2.25 ns; random payloads, which
clamp constantly, 16 to 53 percent faster.
2026-11-07 05:43:54 +01:00
LibretroAdmin e0a07572f0 rwav: read MS ADPCM nibbles as one stream at odd channel counts
An MS ADPCM block's nibbles are one stream, high nibble first, each
frame's channels in turn.  The decoder took the high or low half by
channel parity and stepped to the next byte after each odd channel,
which is the stream for one, two or four channels; with three or five
a frame starts mid-byte, and the decoder read that byte's high nibble
again instead of its low one, so every frame after the first came out
wrong.  The half now follows a running nibble count.

test_rwav encodes IMA and MS at one to five channels with a reference
encoder that keeps its own reconstruction, and checks rwav decodes
exactly that, whole and through windows that start and end inside
blocks.  MS at three channels failed before.
2026-11-07 05:43:51 +01:00
LibretroAdmin f3774dba7c cheevos: hash CISO GameCube/Wii images
rc_hash reads a .ciso file through formats/ciso as the disc image it
stands for, the way RVZ and WIA files are read, so a CISO identifies
as its ISO does. The file reader lives in cheevos/cheevos_ciso.c: it
reads on the descriptor path, straight into the buffer rc_hash gives,
and seeks only when the file is not already where a read starts. The
reader is now built with cheevos as well as with the database.

ciso_hash_test hashes a GameCube disc as an ISO and as a CISO with
blocks left out, and checks the two hashes match.
2026-11-07 04:57:34 +01:00
LibretroAdmin aa3228069f rwav: keep MS ADPCM's step and prediction inside an int
A block states its opening step, and a run of large nibbles grows it
by up to 768/256 a sample with no bound, so a crafted block overflowed
the adaptation's multiply within a few dozen samples: undefined
behaviour, in practice a step that wraps back to small values.  The
step is now capped at INT_MAX / 768, the largest the next adaptation
and the nibble times step both still hold, as reference decoders cap
it.  Past a step of 32767 every non-zero nibble already clamps, so
only streams that were saturated garbage decode differently.

The prediction sums two products of a 16-bit sample and a coefficient
the file states, which reach 2^31 at -32768 each; it is summed in 64
bits and divided as before.

The fixtures decode to the same samples.  test_rwav adds a block of
the largest step and +7 nibbles, which UBSan flagged before and which
now stays pinned at the top.
2026-11-07 04:51:46 +01:00
LibretroAdmin 26765a9e62 audio_mixer: decode coded WAVs straight into the pipeline buffer
A G.711 or ADPCM WAV was decoded to s16 in a scratch buffer of a
sample a channel a frame, then copied or converted into the
pipeline's own buffer and the scratch freed.  For one or two channels
the scratch is not needed:

- s16, stereo: rwav decodes in the pipeline's layout already, so it
  decodes into the buffer and the copy is gone.
- s16, mono: decoded into the first half, then spread to pairs from
  the end, each pair written at or past the sample it reads.
- float: decoded as s16 into the tail of the float buffer (a half for
  stereo, a quarter for mono), then converted front to back; every
  float lands at or before the bytes of each sample still to be
  read, and those are read through memcpy so no float store can be
  ordered ahead of them.

More than two channels still fold from a scratch, which is larger
than the stereo output.  Both pipelines' output is byte-identical to
before for G.711, IMA and MS ADPCM at one, two and three channels;
peak memory while loading drops by the scratch, 12 MB for a 1.5 MB
stereo IMA file.
2026-11-07 04:51:46 +01:00
LibretroAdmin 48b7460a09 test_rwav: ADPCM frames a block bounded by what the block holds
An IMA or MS ADPCM header may state fewer frames a block than its
blocks hold but not more.  Both fixtures state exactly what theirs
hold; the test parses each with that count, a smaller one, one more
and the 16-bit maximum.  Without the bound in rwav_walk the larger
counts parse.
2026-11-07 04:51:46 +01:00
LibretroAdmin 22f1ecad47 audio_mixer: refuse WAV pipelines whose sizes wrap
rwav now bounds a WAV's frame count by what its payload can hold, but
the mixer's pipelines expand that count before allocating: two output
samples a frame, as floats and padded to 16, and for more than two
channels a decode scratch of an s16 a channel a frame.  8-bit mono
into stereo float is eightfold, which a large enough file still wraps
on a 32-bit size_t, and the frame count is kept as an unsigned.  Both
pipeline builders now refuse a count none of those fit in before
sizing anything.
2026-11-07 04:51:46 +01:00
Acts1631 f67b4e33d3 rwav: bound ADPCM samples per block
Reject samples-per-block values larger than a coded block can hold.
Small payloads can otherwise inflate the decoded frame count.

On 32-bit builds, that count can wrap mixer allocation arithmetic and
make decoding write past the resulting buffer.

Also reject overflow when converting the block count to a frame count.
2026-11-07 04:51:45 +01:00
LibretroAdmin a9ac154855 windows_wine_smoke: move the focus with SetForegroundWindow
Both focus-lost lanes have failed since ubuntu-latest moved to 26.04 and
Wine 10, and waiting longer for the release did not help: RetroArch's
window never lost the focus. With no window manager, Wine 10 hands the
activation straight back to the window it holds as the foreground when
xdotool clicks another one, so RetroArch got no WM_KILLFOCUS and
GetForegroundWindow() still named it. A front.exe helper now calls
SetForegroundWindow() on the window being given the focus, alongside
the click.

On 26.04's Wine 10 the two lanes fail without it and pass with it, and
the whole script passes.
2026-11-07 04:43:28 +01:00
Davey Hughes c5be16e09f gfx_widgets: keep queued messages across a driver reinit (#20027)
A reinit that keeps the widgets dropped every message still waiting in
the ring for its first frame. On the first start after an install or
upgrade, the asset extraction reinits video one frame after the first
context init, so the custom Vulkan driver's message never showed.
The ring holds only plain messages (task updates have their own queue),
and gfx_widgets_free() still empties it when the widgets go.
2026-11-07 04:44:27 +01:00
LibretroAdmin 25c54a6919 Savestates: preview thumbnails start from the configured state folder
The playlist entry's savestate thumbnail path took the current
savestate folder as its base.  That folder is whatever the last
content's states were redirected to (states/<dir>/<core> with sorting
on), or empty when no content has run since start, which sent the
lookup to the content's folder even with a state folder configured.
Every content load starts from the configured folder,
RARCH_DIR_SAVESTATE, so the preview does too.

As a load does, the preview leaves sorting alone when there is no
folder to sort under, and when no sorting applies and the folder is
missing it looks beside the content, where states are then written.

gfx_thumbnail_preview_test lays out a stale current folder to check
the configured one is used, and a missing one to check the fallback.
2026-11-07 04:21:13 +01:00
Ryunam f577483a4d Savestates: Fix issues with preview thumbnails 2026-11-07 04:21:13 +01:00
LibretroAdmin cd1a16c44b formats/ciso: CISO reader; scan .ciso GameCube/Wii images
formats/ciso reads a CISO file as the disc image it stands for, with
no file I/O of its own: like formats/rvz, it describes each range of
the file it needs as a request and the caller supplies it. A read asks
for one range per run of stored blocks, straight into the read's own
destination, and blocks left out are zeroed in place; the head lands
in the block map's own storage and the map is built over it. Heads
that are not CISO, PSP CSO included, are refused.

The scanner takes .ciso files through the serial lookup, reading the
disc header through the reader on a descriptor-path stream.
ciso_test covers the reader and the database scan test a GameCube and
a Wii image, and one whose disc starts with a block left out.
2026-11-07 04:18:55 +01:00
davey 2fc159a4e9 android: only load Vulkan drivers picked in the menu
video_vulkan_driver_path is an ordinary config key, and the config is
chosen by whoever launches RetroArch (the CONFIGFILE extra), so a config
could name a driver zip that RetroArch would extract and run.

A pick in the menu now records the zip's path and version in a trust
file in app-private storage. Context init uses the system driver for
any other driver and asks the user to pick it again. When no trust file
exists yet, drivers already extracted are trusted, so current users do
not have to pick them again.
2026-11-07 03:06:17 +01:00
LibretroAdmin d7de9cc7b6 android: open the core path that passed the folder check
runloop_core_dylib_load resolved the core's path to check that it
lies in the app's own folders, then handed the original, unresolved
path to dlopen.  Any symlink on that path could be repointed between
the check and the open.  android_core_path_allowed becomes
android_core_path_resolve, which hands back the resolved path it
checked, and that path is the one opened.  Which cores are allowed is
unchanged.
2026-11-07 03:06:17 +01:00
davey d94f129883 android: only load cores from the app's own folders
A launch intent's LIBRETRO, DATADIR and CONFIGFILE extras are chosen by
whoever sends it, so another app could have RetroArch dlopen any .so.
Every core open now resolves the path and refuses it unless it lies
under the app's dataDir or nativeLibraryDir, or in one of the package's
own code folders in its install folder, all read from ApplicationInfo
rather than from the extras. A Play core install moves the package to a
new code folder while RetroArch runs, so a fixed nativeLibraryDir is
not enough.
2026-11-07 03:06:17 +01:00
Craig c4f751bd99 android: only enable CoreSideloadActivity on debug builds (#20024) 2026-11-07 03:04:46 +01:00
github-actions 76d9622095 Fetch translations from Crowdin 2026-11-07 00:18:09 +00:00
libretroadmin e098569e4d oss: free closes a device that cannot reset
free() returned when SNDCTL_DSP_RESET failed, which is what a device
that has gone away does, leaking the descriptor, the stage and the
driver. The reset is now best-effort and the device is closed and
freed either way.

samples/audio/oss_report wraps close and frees a heap driver whose
reset fails; LeakSanitizer in the CI lane catches the old early return.
2026-11-07 01:00:35 +01:00
libretroadminandClaude Opus 5.5 26f5a00402 net_http: per-request TLS settings for the built-in client
net_http_connection_set_tls() hands a request its own
ssl_socket_retro_config: trust anchors, a client certificate, suites.
Such a request runs on a connection of its own, neither taken from
the pool nor returned to it, so settings never leak between requests
to the same host. With another TLS backend the request fails.

tls_http takes the settings and a sequence of GETs; the local server
matrix covers a client certificate over AES-256-CBC and pool
isolation in both directions against a keep-alive server.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LBQPGdk3hhQeqHc7WNDcYr
2026-11-07 00:59:58 +01:00
LibretroAdmin 270c49dcbd windows_wine_smoke: wait for the release when the focus is lost
Both focus-lost lanes failed in one run with the core seeing B pressed
and never released. The lane closed RetroArch 1.5 s after giving the
other window the focus, and the release reaches the core only on its
next poll after Wine hands the focus over, which a loaded runner can
take past that. It now watches the log for the release, for at most
15 s, before closing.
2026-11-07 00:33:15 +01:00
LibretroAdmin a7dec862c9 d3d11_turns_test: ask again when the core is far ahead
The turns lane failed with 2 of 200 asks finding the core 12 turns
ahead against a limit of 4. The count is read just before the
frontend asks, so a frontend thread taken off its CPU between the two
lets the core run on however fairly it gives way. Such an ask is now
made a second time and counted by the second: the preemption does not
repeat, while a core that does not give way is far ahead again.

Under Wine, 8 runs of the fair core peak at 2 turns; 6 runs of the
unfair one are still caught, 4 to 18 asks over the limit at up to 110
turns ahead.
2026-11-07 00:33:12 +01:00
libretroadmin aac31cf684 net_socket_ssl_retro: per-connection trust, client certificates, RSA and CBC suites
ssl_socket_retro_configure() gives one connection its own settings
before it connects: trust anchors in place of the bundle, whether to
verify the chain and the hostname, the suites to offer, and an RSA
client certificate chain with its key. A 1.2 CertificateRequest is
answered with that chain and a CertificateVerify signed with SHA-256,
SHA-384 or SHA-1 as the server allows, or with an empty Certificate
when there is none to give; a connection presenting a certificate runs
1.2. Such a connection neither resumes a cached session nor leaves one
behind, so no session crosses from one policy to another.

Offered only when a connection names them, the RSA key exchange
(premaster encrypted with rsa_pkcs1_encrypt(), the leaf checked for
keyEncipherment through x509_verify_chain_ex()) and the AES-CBC
suites with HMAC-SHA1/256/384 records, whose padding and MAC are
checked together so that a bad one fails the same way either way. The
default ClientHello is unchanged. These are what the servers of the
Wii U and other consoles of its time expect.

tls_config drives a configured connection in the local server matrix:
own anchors, no hostname check, every new suite over a 1 MB transfer,
a required client certificate from PKCS#1 and PKCS#8 keys under each
signature hash, and a CBC record changed in flight refused.
2026-11-07 00:06:53 +01:00
LibretroAdmin f726f0955a libchdr: bound totalhunks before allocating the v3/v4 hunk map
map_read sized chd->map as sizeof(map_entry) * totalhunks straight from
the header. With a 32-bit size_t the product wraps (0x0CCCCCCD hunks of
the 20-byte i386 entry allocate 4 bytes), and the extraction loop then
writes a full 512-entry chunk past the end of the buffer.

The map follows the header as one record per hunk plus the end-of-list
cookie, so the file size now bounds the count, and the count must also
keep the allocation inside size_t; either failure is CHDERR_INVALID_DATA
before anything is allocated. Regression lane chd_map_alloc_test opens
v3/v4 images from memory, including both wrapping counts with a full
map chunk present.
2026-11-06 23:51:11 +01:00
libretroadmin 1fb7d08c63 libretro-common/crypto: RSA PKCS#1 v1.5 signing and sect233r1
rsa_pkcs1_sign() signs through the CRT with a fixed-window
exponentiation that scans its whole table, and checks the result with
the public exponent before handing it out, so a bad CRT parameter or a
fault cannot leak the factors. SHA-1 and MD5 || SHA-1 (TLS 1.0 and
1.1) join the hashes PKCS#1 v1.5 signs and verifies with.
x509_rsa_private_key_parse() reads PKCS#1 and PKCS#8 keys as views
into the DER.

sect233r1 (NIST B-233), the curve of the DSi, Wii, Wii U and 3DS
certificates and tickets, gets key generation, ECDH and ECDSA signing
and verification: a fixed-length Lopez-Dahab ladder on X/Z, peer points
checked against the curve and the order-n subgroup, and RFC 6979 nonces
with HMAC-SHA256. Its field product runs on PCLMULQDQ or PMULL where
the CPU has them, chosen at run time as crypto.c chooses its GHASH.

The crypto suite checks the RFC 6979 B-233 vectors, an OpenSSL ECDH
vector and the refusal of an order-2n point, and RSA signatures that
match OpenSSL's byte for byte from PKCS#1 and PKCS#8 keys;
crypto_threads_test covers the new CPU check.
2026-11-06 23:08:32 +01:00
davey a580d0f3de runloop: never count more play time than has passed
Closes #20019
2026-11-06 22:32:02 +01:00
davey 8eddbf7b6a x11: load libXss at run time
Closes #20018
2026-11-06 22:24:31 +01:00
LibretroAdmin 52488a9370 rslc: Direct3D bytecode expands calls in place
A call to a function other than main is expanded where it is made, as
fxc does, rather than emitted as a subroutine: Wine's DXBC
translator refuses call and label. The parameters, locals and result
take temporaries above the caller's; an indexed parameter or local
keeps one indexable temporary, the function never being active twice.
A return before the body's end leaves through a loop wrapped around
the body, and one from inside a loop or switch of the body sets a flag
that each enclosing loop and switch breaks out on.

rslc_ext_test builds a stage with early returns, one from inside a
loop, and an indexed parameter, and checks no bytecode it writes holds
call or label.
2026-11-06 19:28:45 +01:00
LibretroAdmin 685857d684 rslc: gl_Layer in a pixel stage
Under RSLC_EXTENDED a pixel stage reads the layer its primitive was sent
to through gl_Layer: SPIR-V's Layer builtin, flat, with the Geometry
capability; SV_RenderTargetArrayIndex in HLSL; [[render_target_array_index]]
in MSL; and in Direct3D bytecode an input after the position, where a
geometry stage writes it, the position declared even when unread so the
two signatures line up. Writing it there is refused.

rslc_ext_test builds a layered pixel stage to every output and checks the
write is refused.
2026-11-06 18:30:28 +01:00
LibretroAdmin 470bff9587 rslc: geometry stages to Direct3D bytecode
A geometry stage now compiles to DXBC for shader model 4.0, 4.1 and 5.0:
the input primitive, output topology and vertex count, the stream (5.0),
instancing through gl_InvocationID (vGSInstanceID, 5.0 only), the input
vertices as two-dimensional v[vertex][register] operands copied into
temporaries where main starts, gl_in's position and clip and cull
distances after the block members, as the vertex stage places them,
EmitVertex as the outputs copied out and emit (emit_stream m0 on 5.0),
EndPrimitive as cut, and gl_Layer as SV_RenderTargetArrayIndex in a
register of its own after the position. A 5.0 geometry stage's outputs
are in an OSG5 chunk.

A geometry stage's interpolated position input is declared
dcl_input_siv and its clip distances plain inputs, as fxc does, and
every output signature element now records the components outside its
mask as never written, as fxc does, so the signatures match its bytes.

rslc_ext_test also builds the two geometry fixtures to bytecode.
2026-11-06 18:09:24 +01:00
LibretroAdmin 66f7174769 rslc: Direct3D bytecode under RSLC_EXTENDED
RSLC_DST_DXBC compiles a vertex, pixel or compute stage to a DXBC
container of shader model 4.0, 4.1 or 5.0 (opts.version 40 / 41 / 50):
the input and output signatures and the program, checksummed, in the
result's spirv / spirv_len words. The interface is the HLSL printer's,
register for register: varyings are packed into signature registers the
way fxc packs that printer's stage structs, so a stage from here links
with one compiled from it, and blocks, samplers, storage buffers and
images take the same slots.

Values live in four-component temporaries, a variable indexed with a
non-constant in an indexable temporary, outputs in temporaries copied
out where the stage ends. Functions are subroutines owning their own
registers, one per binding of their sampler parameters. Workgroup
memory is raw thread-group storage; storage buffers are raw views.
Geometry stages, subpass inputs and subgroup operations have no form
here yet.
2026-11-06 17:16:48 +01:00
LibretroAdmin fdfa08a3b1 rslc: subgroupElect, subgroupMin, subgroupMax and gl_HelperInvocation
Under RSLC_EXTENDED a stage may reduce over its subgroup and a fragment
stage may ask whether it is a helper. SPIR-V takes the GroupNonUniform
instructions at subgroup scope, and a module that uses them is SPIR-V
1.3; every other module stays 1.0. MSL prints simd_is_first, simd_min,
simd_max and simd_is_helper_thread, the last where it is read. GLSL and
HLSL refuse the subgroup operations, HLSL the helper invocation too.

rslc_ext_test checks the bounding box's reduction, and its check target
validates that stage against Vulkan 1.1.
2026-11-06 15:31:46 +01:00
LibretroAdmin 55a8eef700 rslc: subpassInput and subpassLoad under RSLC_EXTENDED
A fragment stage may declare a float input attachment with its
input_attachment_index and read it with subpassLoad. SPIR-V gets a
SubpassData image read at (0, 0) and the InputAttachment capability;
MSL takes it as the colour attachment's value, a [[color(N)]] entry
parameter handed to the functions that read it, as SPIRV-Cross prints
framebuffer fetch. GLSL and HLSL refuse it.
2026-11-06 15:10:04 +01:00
LibretroAdmin ab6b78110b CI: run rslc_ext_test with the other libretro-common samples
The core group of the libretro-common samples job failed with
"Target not classified": rslc_ext_test, added with RSLC_EXTENDED's
array, multisampled and buffer samplers, is built but was in neither
RUN_TARGETS nor NORUN_TARGETS. It runs standalone and passes, so it
joins rslc_test and rslc_build_test in RUN_TARGETS.
2026-11-06 15:07:00 +01:00
Arnold de Ruiterandlibretroadmin e337babce9 core_info: read an info file added after its core was cached, keep served entries in a rewritten cache (#20007)
* Fix core info cache ignoring late .info files and dropping cached entries on rewrite

* core_info: write the cache straight from the core list

The cache writer takes the core list's entries as they are instead of
a deep copy of every installed core, and a core read from its info
file no longer gets copied into a cache list that is not written.

core_info_cache_test also checks that an unchanged core set leaves the
cache alone, and runs under ASan and UBSan in Linux-samples-tasks.

* core_info: read cached entries in place, take core paths from the listing

The cache reader parses each entry straight into the next slot of the
cache list instead of a separate allocation moved in afterwards, and a
firmware list grows geometrically rather than one entry per realloc.
A core's path is taken over from the directory listing, which is freed
at the end of the same pass, instead of being copied.

core_info_cache_test adds a start served from the cache alone, with
more cores than the list starts with and long firmware lists, and a
cache cut off inside an entry, which must be discarded without a leak.

* string_list: add packed splits, use them for core info lists

string_split_packed() splits into a single allocation holding the
list, its elements and the tokens, marked by cap 0 with elements
present. string_list_free() releases it in one free; growing it and
string_list_deinitialize() are refused, so a packed list stays
read-only.

Every pipe-separated core info field is split this way, from the
cache and from info files alike, which takes a warm start over the
full libretro-core-info set from 16661 allocations to 8851. The
string_split_packed sample checks it against string_split and the
refusals, under ASan and UBSan and as a mingw-w64 build under Wine.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-11-06 14:16:58 +01:00
LibretroAdmin 4ecd5f410f rslc: MSL for array and buffer textures, further blocks, storage buffers, distances and compute
RSLC_EXTENDED only, so parallel-gamecubewii's Metal backend can take its
MSL from rslc instead of SPIRV-Cross. Matched against SPIRV-Cross's MSL
for every shader the core generates:

- sampler2DArray, sampler2DMSArray and samplerBuffer as texture2d_array,
  texture2d_ms_array and texture_buffer; sampling, texelFetch and
  textureSize on them; arrays of samplers as array<...> by reference.
- Every uniform block and storage buffer the stage uses, at the buffer
  index of its binding; two the stage uses may not share one. Storage
  buffers are device (const device when read only) with an unsized
  last member of one element, and their atomics atomic_*_explicit. A
  block's bool is a uint, turned back on read; a struct member is taken
  when it lays out as the block does.
- gl_ClipDistance / gl_CullDistance in a [[clip_distance]] array and
  each as a user(clipN) varying.
- A compute stage: kernel main0 with the thread position builtins,
  workgroup memory as threadgroup C arrays, images as texture2d(_array)
  with their access, imageLoad / imageStore / imageSize and the barriers.
- Only the inputs, blocks and samplers the stage uses are declared, as
  Metal wants a vertex descriptor entry for every attribute named; both
  outputs of a dual-source pair name their index.

In every build, a case section of a switch that declares something and
has another case after it is a block in MSL, as C++ forbids jumping past
the declaration; outside fragment stages texture() samples level 0 in
MSL and HLSL. The slang-shaders corpus compiles byte-identical to before
in the stock build.
2026-11-06 14:11:01 +01:00
LibretroAdmin e98e1da005 vfs smb, scan: read every smb:// URL from the configured server
The VFS reads the configured server whatever host a URL names, so a
scan or refresh no longer compares the host: a playlist saved under an
older server address refreshes after the SMB server setting changes,
and the check takes no name lookup. A share other than the configured
one is still refused.

vfs_test checks URLs of other hosts, unresolvable ones included, pass
on their share alone.
2026-11-06 05:15:15 +01:00
davey c4fb538a89 scan: don't wait on an SMB server on the main thread
From the menu, the main thread stat'd an smb:// content folder up to
three times before its scan task ran: for the playlist name, for
whether it is a file and for the scan's folder flag. Manual Scan, Scan
Directory, Scan File and a lone Refresh Playlist each did, and against
a server that does not answer each stat waits out a connect with the
menu frozen.

The manual scan settings now keep what the caller knows the content
folder to be. The menu's folder pick, Scan File and Scan Directory say
whether it is a folder or a file, a refresh says its saved folder is a
folder, and --scan and the chain's refreshes take it from the worker's
read of the path. For an smb:// path of known kind those three places
use the kind instead of a stat: the menu paths stat it none at all on
the main thread, and a --scan or refresh of a reachable share is not
stat'd again when its scan is pushed. The kind is kept with the
settings a scan chain saves and puts back. A local path, and an
smb:// one of unknown kind, stats as before.
2026-11-06 05:15:14 +01:00
davey 6ef3af70f8 scan: say why an smb:// folder failed; refuse another host or share
A scan or refresh of an smb:// folder that could not be read said
"not found" or "skipped: content folder not found", whatever the
cause, and logged nothing; in the menu, a scan scanned the folder path
as a single file. A URL naming another host or share than the SMB
settings was read from the configured server and share all the same.

The scan task, the --scan path task and the playlist load of a
refresh now check such a folder on the task worker before the scan
starts: no SMB server set, a URL host or share other than the
settings', or, when the folder cannot be read, the share's recorded
connect failure (name lookup, connect, login, share). A URL refused
for its host or share is never stat'd by them. The reason goes to the
menu notification, one log line and the --scan line or the refresh's
skip line. The item still fails, or the playlist is still skipped and
left untouched, as before. A folder the server reports missing keeps
the message it had.

The man page states the host and share rule.
2026-11-06 05:15:14 +01:00
davey a2bd2a71db vfs smb: keep a failure code per share; check a URL against the settings
A failed connect kept only the client's error text, the last of any
thread's, so a caller of stat could not tell a wrong password from a
missing folder.
Each pool now keeps the outcome of its last connect: connected, or the
step that failed (name lookup, connect, login, share), from the
built-in client's phase or libsmb2's NT status. smb_url_failure()
reads it for a URL without I/O or taking a use.

The VFS also connects to the configured server whatever host a URL
names, and a configured share overrides the URL's, so a scan of
smb://otherhost/... reads the configured server and saves the other
host into the playlist. smb_url_check() says whether a URL names the
configured server and share: hosts compare as text, ignoring case and
without a user@ prefix, a port or IPv6 brackets, and only on a
mismatch are both names looked up; the share is compared only when
one is configured. It may block on that lookup, so it is for worker
threads.

vfs_test checks each failure code against a local smbd, frozen server
included, and the URL checks without a server, the URLs the menu
builds from the settings included. vfs_threads_test reads the code
beside the pool evictions under TSan.
2026-11-06 05:15:14 +01:00
davey 1165a9f1f6 net_smb2: remember which connect step failed
rsmb_connect() kept only an error string and the last reply's status,
so a caller could not tell a wrong password from a missing share or a
server that is not there. The context now records the step a failed
connect was on (lookup, TCP connect and negotiate, session, tree
connect; a send or receive that fails counts as the connect), read
with rsmb_get_phase(), and smb2_get_phase() for the VFS's compat
layer. smb_test checks each step against a local smbd.
2026-11-06 05:15:14 +01:00
LibretroAdmin 5ee7021f0b rslc: fold the frontend block-name test in the default build
Without RSLC_EXTENDED a stage holds only the uniform and push blocks,
so pr_block_instance's index test is a compile-time true and GCC no
longer threads an out-of-range path into prog.blocks[] (-Warray-bounds).

The rslc sample builds at -O2 with -Werror=array-bounds so the bounds
checker sees the default build's fixed-size block arrays.
2026-11-06 04:47:12 +01:00
Ju Huoandlibretroadmin ec750f8ee5 Add optional composite sync support to CRT SwitchRes (#19995)
* Add optional composite sync support to CRT SwitchRes

* CRT SwitchRes: fall back to separate sync where CSync cannot apply

An invalid csync value, or a display server without composite sync,
now falls back to separate sync with one warning instead of aborting
every mode switch, which left the desktop's separate sync on the wire
anyway and broke the EDID writer whenever csync was set. With csync
off, listed modes are selectable as before, polarity included; only
the modes generated here are held to the requested polarity.

XRandR takes over a same-named RA mode only when its timing is
identical, so a previous session's leftovers are reused and cleaned
up, and suffixes the name otherwise; the name scan uses one resources
fetch. The CRT sample and the live Xorg sample cover both.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-11-06 04:43:19 +01:00
LibretroAdmin 2d16d04560 rslc: HLSL for in / out blocks, clip distances, storage buffers and compute
RSLC_EXTENDED only, so parallel-gamecubewii's D3D backends can take
their HLSL from rslc instead of SPIRV-Cross. Each piece is laid out the
way SPIRV-Cross lays it out, so the backends bind what they bound:

- An in / out block is a static of its struct; its members travel as
  Block_member fields, one TEXCOORD location each from the block's.
- gl_ClipDistance / gl_CullDistance travel in one floatN with an
  SV_ClipDistance0 / SV_CullDistance0 semantic (four at most).
- A storage block is a ByteAddressBuffer at t<binding> when readonly and
  an RWByteAddressBuffer at u<binding> otherwise, named after the block;
  reads Load at the member's byte offset, an element that is a struct
  through a loader made for it, and the atomics through Interlocked
  helpers. A plain store has no form yet and is refused.
- A compute stage: [numthreads], SV_DispatchThreadID, SV_GroupThreadID,
  SV_GroupID and SV_GroupIndex, groupshared memory, the barriers, and
  four-component storage images as RWTexture2D(Array) at u<binding>.
- A cbuffer may hold a struct whose members are all four-wide vectors,
  which packs alike in HLSL and std140.
- A vertex input takes no interpolation modifier, and a buffer or
  multisampled texture no sampler state.

Every shader parallel-gamecubewii generates for D3D, the ubershaders, the
utility shaders, the texture decoders and a sample of specialized ones,
prints HLSL that glslang's HLSL front end accepts. The slang-shaders
corpus compiles byte-identical to before in the stock build.
2026-11-06 04:23:06 +01:00
LibretroAdmin 982f147de2 rslc: compute stage, storage images, shared memory and barriers
RSLC_EXTENDED only, for the texture decoders parallel-gamecubewii runs
on the GPU:

- RSLC_STAGE_COMPUTE, its layout(local_size_x/y/z) in; (1 when unset)
  and gl_GlobalInvocationID, gl_LocalInvocationID, gl_WorkGroupID,
  gl_NumWorkGroups and gl_LocalInvocationIndex.
- image2D and image2DArray in float, int and uint, with their format
  in the layout and readonly / writeonly; imageStore, imageLoad and
  imageSize.
- shared globals, in Workgroup memory.
- barrier, memoryBarrier, memoryBarrierShared, memoryBarrierBuffer,
  memoryBarrierImage and groupMemoryBarrier, with glslang's scopes and
  semantics.

SPIR-V and GLSL; HLSL and MSL refuse the stage. Every decoder the core
generates compiles, passes spirv-val, and prints GLSL glslang accepts.
The slang-shaders corpus compiles byte-identical to before in the stock
build.
2026-11-06 04:23:04 +01:00
LibretroAdmin 996746613a rslc: unnamed in / out blocks, gl_in, bools in blocks, DepthReplacing
What parallel-gamecubewii's shared utility shaders need, every one of
which it compiles when its Vulkan backend starts:

- An in / out block may go without an instance name; its members are
  then named on their own (RSLC_EXTENDED).
- A geometry stage's input instance[] takes its length from the input
  primitive, and gl_in[] is there with gl_Position, gl_PointSize and the
  clip and cull distances, the arrays as long as the highest element
  read (RSLC_EXTENDED).
- A bool, bvec in a uniform or storage block is held as a uint, uvec in
  SPIR-V, as Vulkan has no bool in memory; reads compare against zero,
  writes select 1 or 0.
- A fragment stage writing gl_FragDepth declares DepthReplacing, as
  Vulkan requires.

The slang-shaders corpus compiles byte-identical to before in the stock
build.
2026-11-06 04:23:00 +01:00
LibretroAdmin 37de5433eb rslc: storage buffers and atomics
Behind RSLC_EXTENDED, for parallel-gamecubewii's bounding box and its
dynamic vertex loader:

- buffer Name { members } [instance]; with readonly, writeonly,
  coherent, restrict and volatile: std430 unless it says std140, at
  its own set and binding, its last member possibly an array of
  unstated length. SPIR-V 1.0 has it as a BufferBlock in the Uniform
  class, an unsized member as a runtime array, and readonly and
  coherent as NonWritable and Coherent on the members, as glslang
  writes them. A member of a writable buffer is an lvalue.
- atomicAdd, atomicMin, atomicMax, atomicAnd, atomicOr, atomicXor,
  atomicExchange and atomicCompSwap on an int or uint member of a
  writable buffer, at device scope with no ordering.
- a further uniform block is std140 whether or not it says so.

GLSL prints the buffers; HLSL and MSL refuse them yet.
samples/gfx/rslc_ext adds the core's bounding box and vertex-fetch
shapes; spirv-val takes both.

Without the define the slang-shaders corpus compiles byte for byte as
before to every destination; the instructions it takes differ by
0.06%.
2026-11-06 04:22:56 +01:00
JCorrêaandLibretroAdmin eb699ab842 fix(ps3): overlap core rendering with RSX flips while preserving VSync (#20003)
* fix(ps3): overlap core rendering with RSX flips while preserving VSync

Wait for the pending flip before frontend composition so the core can
render while the flip is in progress. Keep flip completion synchronized
when menu or overlay uploads are active.

With VSync enabled, present the cached image for duplicate frames even
when no UI is visible. This prevents emulation from running too fast
when audio sync is disabled.

Cover flip timing and software and hardware frame duplicates with and
without visible UI in the RSX regression tests.

* rsx: keep the core behind the flip with two swapchain images

Max Swapchain Images now chooses the RSX queue depth: two keep the
CPU behind each vsync flip for the lowest input latency, three let
the core run into the pending flip. The setting is shown for RSX.

A vsync dupe redraws the last frame that loaded; a failed upload no
longer becomes the cached frame, and with nothing cached the dupe
leaves the screen as it is instead of presenting a cleared buffer.

rsx_alloc_test models the GCM flip status, so a wait on a flip that
was never reset or queued shows up, and gains flip_latency and
dupe_keeps_image lanes.

* PS3: default to two swapchain images

RSX keeps the core behind each vsync flip with two images, the lowest
input latency; three, letting the core run into the pending flip,
stays available as a setting.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-11-06 04:22:36 +01:00
LibretroAdmin 01a14c020d drm_vkms_live: fetch the newest kernel the archive can serve
resolute-updates published linux-image-generic 7.0.0-43 before the
linux-image-unsigned and linux-modules packages it names, so run_vm.sh
took 7.0.0-43 and could not download it. hwe_kernel() now walks every
version of the metapackage, newest first, and takes the first whose
unsigned image and modules both have a candidate: 7.0.0-42 today, 43
once it is published. The CI cache key comes from the same function.
2026-11-06 03:56:42 +01:00
LibretroAdmin 8b9874e427 vfs smb, nfs: give a last-error slot to one writer at a time
smb_cfg_race_test failed under TSan: two reader threads wrote the same
smb_err_ring slot. The slot came from a counter taken modulo the ring
size, so once the counter wrapped, two writers still filling their
messages could land on one slot. Each slot now has a busy flag a writer
takes with a compare-and-swap, moving on to the next slot when it is
taken, and the slot last published is skipped, since a reader may be
holding it. With every other slot in use the message goes unrecorded.
NFS fills its ring the same way and gets the same change.

smb_cfg_race_test failed 7 of 8 runs under TSan before and 0 of 20
after; nfs_cfg_race_test and nfs_error_test pass.
2026-11-06 03:56:38 +01:00
LibretroAdmin 3038603c62 oss: a device whose select() does not wait for room waits in its write
RetroFW's jz4760 OSS driver has no poll method, so select() reports the
descriptor ready at once whether or not a fragment is free. Eight wakes
without room now latch that: from then on wait_writable() claims the
room and the blocking write sleeps in the driver on its fragment-done
interrupt, rather than handing back 0 for the audio thread to ask again
at once - at real-time priority on a single core, nothing else ran.
A non-blocking descriptor claims nothing. Logged once at teardown.

samples/audio/oss_report models that driver with wrapped ioctl, select
and write: a full poll-less device hands every pass to the write, and a
device whose select() does wait still skips the pass.

Fixes #20006
2026-11-06 02:38:50 +01:00
LibretroAdmin 749c746a8e rslc: geometry stage and in / out interface blocks
Behind RSLC_EXTENDED, for parallel-gamecubewii, which runs a geometry
stage for wide lines, points and stereo, and passes its varyings
through interface blocks whenever one is in use.

- in / out Name { members } instance[N]; in every stage: a struct
  variable at the block's location, decorated Block in SPIR-V, each
  member keeping its own flat / noperspective / centroid / sample
  (an integer member of a fragment input is flat). GLSL prints the
  block as written; HLSL and MSL refuse one yet.
- RSLC_STAGE_GEOMETRY: layout(points | lines | lines_adjacency |
  triangles | triangles_adjacency, invocations = N) in;,
  layout(points | line_strip | triangle_strip, max_vertices = N) out;,
  an input block as an array with one element per vertex,
  gl_Position, gl_PointSize, gl_ClipDistance, gl_CullDistance through
  gl_PerVertex, gl_InvocationID, gl_Layer, EmitVertex() and
  EndPrimitive(). SPIR-V and GLSL; HLSL and MSL refuse the stage.

samples/gfx/rslc_ext compiles a wide-line geometry stage shaped as
the core generates it, and blocks between a vertex and a fragment
stage; spirv-val takes every module and glslang the printed GLSL.

Without the define the slang-shaders corpus compiles byte for byte as
before to every destination; the instructions it takes differ by
0.05%.
2026-11-06 01:50:03 +01:00
LibretroAdmin 0195f5fa80 rslc: gl_ClipDistance and gl_CullDistance in a vertex stage
Behind RSLC_EXTENDED, for parallel-gamecubewii, whose vertex shaders
write two clip distances whenever depth clamping is in use.

Each array is as long as the highest element the stage writes, as
glslang sizes an implicitly sized one; the index must be a constant
below 8, the least maxClipDistances Vulkan guarantees. The arrays sit
in gl_PerVertex at those lengths, with the ClipDistance and
CullDistance capabilities only when written. GLSL prints them as
written; HLSL and MSL have no form of them yet and refuse the stage.

Without the define the slang-shaders corpus compiles byte for byte as
before to every destination, in the same time.
2026-11-06 01:29:55 +01:00
LibretroAdmin 777f19820e rslc: further uniform blocks, sampler arrays, dual-source outputs
More of what parallel-gamecubewii's shaders need, behind RSLC_EXTENDED
like the samplers before it:

- more than one uniform block in a stage: each further block takes a
  slot past the push block and keeps its own set and binding (HLSL
  register(bN) at the binding). The reflection tables still describe
  only the first uniform block and the push block.
- sized arrays of samplers at one binding, indexed by constants or by
  any expression; HLSL indexes the texture and its sampler state
  alike.
- layout(index = 1) on a fragment output, for dual-source blending:
  SPIR-V Index, GLSL index =, HLSL the next SV_Target, MSL index(1).
- layout(early_fragment_tests) in;: SPIR-V EarlyFragmentTests, HLSL
  [earlydepthstencil], MSL [[early_fragment_tests]].

MSL refuses a third block. The block count stays at two without the
define, and the slang-shaders corpus compiles byte for byte as before
to every destination, and no slower.
2026-11-06 01:15:07 +01:00
LibretroAdmin 15e62926d9 rslc: array, multisampled and buffer samplers behind RSLC_EXTENDED
A build with RSLC_EXTENDED defined takes what an emulator's own
shaders need and the frontend's never do. This is the first part,
for parallel-gamecubewii, whose generated shaders go to rslc in place
of glslang and SPIRV-Cross:

- sampler2DArray, sampler2DMSArray and samplerBuffer, each with its
  i and u forms
- texture, textureLod, texelFetch and textureSize on them: an array's
  level and a multisampled image's sample go where SPIR-V, GLSL and
  HLSL want them, and a multisampled or buffer image's size is
  queried without a level
- gl_SampleID, which makes a stage that reads it run per sample
  (SampleRateShading, flat)

SPIR-V, GLSL and HLSL print all of it. MSL has no form of the new
samplers yet and refuses them with an error.

Without the define those names are not keywords and the new
overloads are not in the builtin table. The 3346 stages of
libretro/slang-shaders compile to SPIR-V, GLSL 450, HLSL 50 and MSL
byte for byte as before, and no slower.

samples/gfx/rslc_ext tests every form; its check target also runs
spirv-val on the modules when it is installed.
2026-11-06 01:15:02 +01:00
LibretroAdmin dcd42cec7e threaded_video_test: pace the frame memory lane at a display period
The macOS metal-harness failed "frame memory lane, copied frames" with
17 of 40 frames copied by the CPU, on a commit that touched only
.gitlab-ci.yml; the run before it passed. The lane pushed its 60 frames
back to back. The video thread hands a frame buffer back on the pass
after the GPU has finished with it, so on the runners' paravirtual GPU
every one of the driver's buffers could still be in flight at the next
push, which then took a host buffer: the GPU's latency, not the path
the lane checks. The frames now go a display period apart, as a core's
do.
2026-11-06 00:37:28 +01:00
CraigandLibretroAdmin 70d13dda2c android: add quest build (#19968)
* android: add quest build

* android: quest builds through the existing Android lane

Android.yml's assembleDebug already builds, alignment-checks and uploads the questDebug APK, so the separate workflow is dropped. The GitLab release job stays.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-11-05 22:44:37 +01:00
LibretroAdmin 8a816f0148 CI: run the i686 container on Ubuntu 24.04
Since ubuntu-latest moved to 26.04, CI Linux (i686) stops at checkout:
git fetch cannot resolve github.com from the 32-bit xenial image, ten
tries in a row, and the public resolvers the job passes with --dns do
not change that. The 64-bit container jobs on the same runners check
out and build. The job goes back to ubuntu-24.04, where it last
passed, like the mipsel lanes.
2026-11-05 22:12:33 +01:00
LibretroAdmin 21e43db393 CI: measure the hot-function budget under GCC 15
menu-harness failed its hot-function budget on Ubuntu 26.04:
audio_driver_sample_batch came out at 623 bytes against a 512 ceiling,
with runloop_iterate and video_thread_frame a few bytes short of
theirs. Built from the same tree, GCC 13 emits 511, 6911 and 4319
bytes for the three and GCC 15 623, 8026 and 4825, so the step is the
compiler and not the source. The reference now holds what GCC 15
measures on the runner, frames included; the quarter of slack above it
is for the next real change.
2026-11-05 22:07:10 +01:00
LibretroAdmin 33c6ef9258 samples: FFmpeg 8 colour tags, and the AV1 hardware tests' MP4 writer
webm_av1_hw_test and preview_av1_hw_test still called make_mp4, which
the last change removed, and took the MP4 writer through
rav1_webm_fixture.h, which now asks for the byte helpers alone. Both
include rav1_mp4_fixture.h first for the whole writer and call
make_mp4_clip(1, 0).

FFmpeg 7.1 and later take a frame's primaries and transfer into the
encoder over the -color_primaries and -color_trc output options, so
libaom, libvpx and libx265 without its own colour parameters write
untagged streams. mp4_mux's HDR sources lost their transfer and run.py
stopped on the missing key; the frames are now tagged with setparams.
The same goes for the hand-run generators that tag through those
options alone: gen_av1_decode.py's colour stream, the D3D11 HDR AV1
fixtures and mkhdrfixtures.py's VP9 movies.

FFmpeg 8's concat also converts every segment to the first one's
colour space, so the ffmpeg_live colour-metadata lane could no longer
switch from BT.709 to BT.601 inside one lavfi graph: the second half
arrived converted and labelled BT.709. The lane now plays one BT.709
segment and its receive_frame wrapper relabels the frames after the
third as BT.601, which is the transition the driver has to follow.

The webm_player timeline-gap fixtures cut their silent second with
aselect before the encoder, and FFmpeg 8 closes that gap: the next
frame takes the previous one's end, so the packets ran on unbroken and
the seek into the gap found signal. The packets are now dropped after
the encoder with the noise bitstream filter, which keeps every
timestamp and gives the same packets on FFmpeg 6.1 and 8.
2026-11-05 21:10:54 +01:00
LibretroAdmin 7d2e95318b video_shader: measure the config directory where it is written
gcc 15 threads a path into the reference-preset check on which it sees
config_dir at address zero and warns that strlen reads past it in every
build. The length is taken once, right after the directory is filled.
2026-11-05 19:56:40 +01:00
LibretroAdmin a4172fdafd CI: the lanes Ubuntu 26.04 broke, and the formats warnings
Built with ffmpeg 8, libaom 3.13, x265 4.1, glibc 2.43 and clang 21,
five lanes failed on the 26.04 runners:

- AV1 fixtures: a monochrome sequence comes out full range with no
  matrix whatever is asked for; the oracle takes the colour ffprobe
  reads instead of assuming it.
- Preview audio oracle: ffmpeg 8 leaves a timestamp gap at every seam
  of a -stream_loop copy, which the mixer honours and ffmpeg's raw
  decode does not. trailing_large loops its picture and encodes its
  sound as one track.
- rh265 multi-slice oracle: x265 4.1 writes unreadable slice headers
  when two slices are encoded on several threads; those cases encode
  on one worker.
- vkms: the kernel follows the runner's own release, its modules may
  sit under /usr/lib/modules, and modules-extra is fetched only where
  it exists.
- clang -std=c89 -pedantic: glibc's const-generic strchr/strstr under
  _GNU_SOURCE are _Generic; -Wno-c11-extensions goes wherever
  -Wno-c99-extensions already does.

ci_paths filters a file at the top of a samples tree by its own path.
rh264 and rh265 lose the parameters no instantiation reads, the 8-bit
H.264 kernels mark the depth they ignore, and the AV1 WebM fixtures
leave out the MP4 box writer they never use.
2026-11-05 17:36:02 +01:00
LibretroAdmin de1def158d rslc: add a builder API for generated shaders
A program that generates its shaders today prints GLSL and has rslc
parse it again.  rslc_build.h lets it make the same stage by calls:
types, inputs and outputs, the uniform and push blocks, samplers,
functions, expressions and statements, then rslc_b_end emits any
rslc destination.  Nothing is printed or parsed on the way.

The builder sits in rslc_front.c, under RSLC_BUILD, and reuses the
parser's own typing helpers: implicit conversions, builtin overload
resolution and constant folding are the same code, so a stage built by
calls compiles to the same words as the same stage written as GLSL.
Statement makers return nodes, so an if, loop or switch takes a single
statement or a block alike.  An error poisons the build: every later
call returns 0 and rslc_b_end reports it, so a caller checks once.

Without RSLC_BUILD nothing changes: rslc_front.o's text is the same
size and the griffin build and size budget are untouched.

samples/gfx/rslc_build writes a vertex and a fragment stage both ways,
covering blocks, matrices, swizzles, structs, const arrays, out
parameters, every loop kind, switch, discard, ternaries and texel
fetches, and checks the SPIR-V, GLSL and HLSL are identical; it also
checks the error paths.  Claimed in the samples workflow.
2026-11-05 17:07:22 +01:00
LibretroAdmin 6595ed70f4 CI: install packages Ubuntu 26.04 resolves
On 26.04 qemu-user-static is a virtual package with two providers, so
apt refuses it and the cross jobs fail before building. qemu-user ships
the static emulators there, as qemu-aarch64, qemu-arm and qemu-ppc, the
names 24.04's qemu-user uses too; the cross jobs install it and run
those. libfreetype6-dev is asked for by its real name, libfreetype-dev.
2026-11-05 02:57:58 +01:00
github-actions 37d73dd4b5 Fetch translations from Crowdin 2026-11-05 00:41:41 +00:00
LibretroAdmin 24457eef48 S3: keep the path buffers off the stack
s3_backup_then held six path buffers on the stack, 12.8 KiB, and
s3_read and s3_delete_object two each, a little over 4 KiB. The backup
names now go in one allocation, and the URL and canonical URI of an
object come from s3_object_urls, one allocation holding both, which
s3_update_upload uses too. The largest frame in the driver is now
s3_build_request_url's 2.1 KiB. The hand-copied empty payload hash in
the GET and DELETE goes to S3_EMPTY_PAYLOAD_SHA256.

s3_backup_test fails those allocations for a backup before an upload
or a delete, a plain upload, a delete and a read: nothing starts,
nothing is reported, nothing leaks.
2026-11-04 22:46:27 +01:00
LibretroAdmin 890f0c1106 samples/s3_backup: link Winsock on Windows, keep temp files local
The test never linked under MinGW: net_http.c and its sockets need
ws2_32, which the Makefile left out, as the other network samples do
not. It also wrote its scratch files under /tmp, which a native Windows
build resolves to \tmp on the current drive, usually missing. They go
in the current directory now, and clean removes the .exe too.
2026-11-04 22:43:03 +01:00
LibretroAdmin 9d85133572 S3: stream uploads from the file, not a whole-file copy
An upload read the whole file into one malloc before it started: up to
the 64 MiB multipart threshold for a single PUT, and the whole file,
however large, for a multipart upload, held until its last part was
acknowledged. Out of memory the upload failed, and on the consoles the
threshold is sized for, that is not far off.

The body is now pulled from the file by net_http as the socket takes
it, through net_http_connection_set_content_source, the way WebDAV
already uploads: one send buffer instead of the file. Each multipart
part is a byte range of the file, seeked to on its first run so it
reads its own bytes whatever was read before it, and a replay on a
fresh connection rewinds to the range's start. The payload hash for the
signature is taken by reading the range in 64 KiB runs into
sha256_stream_update. The fallback to a single PUT when the multipart
start fails in transport streams the whole file the same way.

task_push_http_transfer_with_source is task_push_http_transfer_with_
content with a source in place of the buffer. The upload's URL buffers
move off the stack (4.2 KiB to 112 bytes).

s3_backup_test's stub drains every streamed body as net_http would,
with a replay from the start, and checks it is all there and is what
was signed. New lanes: an 11 MiB multipart upload (three parts, each
its own range of the file), its single-PUT fallback, and an empty file.
The multipart threshold can be set by the test for that.
2026-11-04 22:36:51 +01:00
LibretroAdmin 44090f5ea9 S3: sign with the shared SHA-256 and HMAC, streamed
The SigV4 signer assembled the canonical request and the string to
sign in 4 KiB stack buffers and MACed them with its own HMAC. A URI
or copy source past 4 KiB was signed cut short, and so was a secret
key over 123 octets (the setting takes 185), so those requests came
back 403. The two buffers also put the signer's frame at 9 KiB.

The canonical request now goes straight into sha256_stream_update as
it is made, and the string to sign into hmac_sha256_update, both from
crypto/kdf.c: nothing is assembled, so nothing is cut short. The query
is sorted as an index of spans over the caller's string, on the stack
up to 16 parameters, so the signer allocates only its result. The
hand-written HMAC, the query copy and the old signature helper go.

HAVE_S3 now needs HAVE_CRYPTO, which every build that defines it
(Apple) already has; griffin says so if it is missing.

s3_backup_test checks seven signatures against an independent SigV4
computation, including a 5001-character URI and a 150-octet secret,
which master signs wrong. The query and out-of-memory tests follow the
new shape, and the test rebuilds when the driver changes.
2026-11-04 22:36:51 +01:00
LibretroAdmin a7b2a2ef96 nfs: READ data lands in the caller's buffer, not in rx first
A READ reply is read as it is parsed: the RPC and READ headers into
c->rx, the data itself straight to where the octets go, for v3 READ,
v4 READ and each data run of READ_PLUS, across fragment marks. A reply
the fast path does not take - an error, GRACE or DELAY, READ_PLUS
refused, a denied call - is read whole into c->rx and parsed as
before. Nothing is asked of the socket that the reply has not promised,
so a short reply fails rather than waits.

Lanes: local_read_plus_test.sh reads the sparse file again from
fake_nfs42.py with every reply cut into 333-octet fragments and an
empty last one, as READ_PLUS and, with READ_PLUS refused, as READ.
2026-11-04 22:01:59 +01:00
665064a6f4 Android: custom Vulkan driver packages (Turnip) on Adreno (#19955)
* Android: load custom Vulkan driver packages on Adreno

Sideloaded arm64 builds can load a Vulkan driver package (a zip with
meta.json and one .so, such as Mesa's Turnip) on Adreno GPUs with
Android 9 or later. As libadrenotools does, a private copy of the
system Vulkan loader is opened in a linker namespace of its own, where
a small hook library hands it the custom driver as its HAL. The new
video_vulkan_driver_path setting names the zip; empty is the system
driver, and core, content-directory and game overrides carry it. The
package is checked and extracted to internal storage once per version.

Packages from any vendor are accepted. Only Mesa ones have been
tested, so any other vendor logs that it is untested and that its load
cannot be confirmed. A Mesa package that leaves the stock Qualcomm
driver running, as the hook does when the custom library fails to
load, counts as failed.

When the custom driver fails to create an instance, a device or the
first swapchain, the context is redone on the system driver in the same
process. A driver that takes the process down during its init is turned
off for that core until it is picked again, and a "Safe start" launcher
shortcut starts RetroArch once on the system driver. Play Store, Quest
and API 16 builds do not carry any of it.

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

* Menu: Custom Vulkan Driver setting

Settings > Video > Output gains a Custom Vulkan Driver row on builds
and devices that can load one. OK opens a browser for driver zips in
<system>/vulkan_drivers, Start goes back to the system driver, and the
sublabel says when the chosen driver crashed during its last start and
is off for the running core. While RetroArch runs from the Safe start
shortcut, the row reads "System (Safe start)" and the sublabel names
the driver the next normal start uses. The choice applies at the next
Vulkan start; picking a driver never restarts video.

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

* archive: a streamed Deflate member writes no more than its size

A Deflate member streamed to its file a window at a time is now
refused as soon as it inflates past the size its headers give, so no
more than that size reaches the disk, as for Deflate64, LZMA, zstd and
bzip2. Both the zlib and the built-in inflate paths.

Lane: archive_stream_test "inflate bound", a member of stored blocks
that holds 1 MiB under a 300 KiB size, counting what the VFS writes.

* Vulkan custom driver: merge onto master, ask the GPU once

The menu row moves one entry up its table, clear of the manual-scan
row master renamed. Whether the device can take a custom driver is
asked once and kept, rather than each time the Video settings list is
built.

* Vulkan custom driver: extract a picked package in a task

Picking a driver pushes a task that checks the package and extracts
it on a worker, into the dir the next Vulkan start looks in, so that
start only maps the library rather than inflating up to 64 MiB while
the main thread waits. The task reads only what it was handed.

It writes through a .prep dir of its own; a context init that gets
there first extracts through its .tmp as before, and whichever
finishes second keeps the other's. The once-per-process prune leaves
.prep dirs alone while a task runs. A driver set by an override,
never picked, is still extracted at its first start.

---------

Co-authored-by: lim2222 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-11-04 21:46:42 +01:00
LibretroAdmin 18788f0c40 CI: run the mipsel lanes on ubuntu-24.04
Ubuntu 26.04 carries no MIPS cross toolchain, so on ubuntu-latest
apt-install.sh cannot resolve gcc-mipsel-linux-gnu and the compile
matrix and libretro-common test jobs fail before building anything.
Both jobs run on ubuntu-24.04, where the dingux mips lane and the
crypto suite under qemu-mipsel keep running.
2026-11-04 21:24:00 +01:00
Davey Hughes 51d84f0d3d gl: report views from init, not from the first frame (#19953)
gl2 published whether it can draw views only from gl2_frame(), so
until its first frame it reported none. At content start and after
each video driver reinit, a core asking for the views status on its
first retro_run() was told views are neither presented nor stereo,
and that frame was not laid out as views: one frame of the core's
own layout, or of its packed frame shown whole.

gl2 now publishes it at init, as vulkan does.
2026-11-04 20:58:40 +01:00
LibretroAdmin 86d9bbc6c0 samples: scan_begin_budget_test runs on native Windows
The test built its fixture under $TMPDIR or /tmp and removed it with
system("rm -rf ..."): a native Windows build has neither /tmp nor a
POSIX shell behind system(), so make check stopped at "could not build
fixture under /tmp/scan_begin_fixture".  The fixture goes in the
temporary folder (TEMP or TMP on Windows, TMPDIR elsewhere, else /tmp)
and is removed with rmdir /s /q on Windows.
2026-11-04 19:53:43 +01:00
LibretroAdmin 47ec4f5d29 samples: the second scan run in one root, and a quiet cleanup
make check runs database_scan_test twice in one root.  The "fs off
main" lane deleted the playlists it wrote in its playlist folder but
not the one outside it, so the second run's write of that playlist
pushed an entry already on top, the push refused it, and the lane
failed at its fixture.  It deletes both now.

playlist_parity_test's cleanup let cmd.exe print what rmdir could not
find or remove; the cleanup is best effort, so it says nothing now.
2026-11-04 19:53:43 +01:00
LibretroAdmin 0b5ea102f5 samples: the database sample's check runs on MSYS2
make check handed the tests the POSIX path mktemp gives, and none to
the index test, which then used /tmp.  Under MSYS2 the tests are
native Windows programs and could open neither, so the run stopped at
the index test's "test databases could not be written".  Each test now
gets its own fresh root, put through cygpath -m where there is one;
elsewhere the path is unchanged.
2026-11-04 19:53:43 +01:00
LibretroAdmin 491357989b samples: playlist_parity_test's path checks hold on Windows
The autofix lane and the two failed-copy lanes compared paths with the
POSIX strings they pushed.  playlist.c stores what playlist_resolve_path()
makes of them, which on Windows is "C:\games\game.bin" for
"/games/game.bin", and autofix writes the rest of a rebased path in the
local form, "/mnt/newgames\dir000\...".  So 15 checks failed there
while the copies they test behaved.

The failed-copy lanes now compare with the path as a push stores it,
through playlist_resolve_path() itself; the autofix lane takes either
slash after the new base on Windows.
2026-11-04 19:53:43 +01:00
LibretroAdmin 9b546c8a7c samples: playlist_parity_test runs on native Windows
The test made its fixture with system("mkdir -p /tmp/...") and removed
it with "rm -rf": a native Windows build has no /tmp and its system()
runs cmd.exe, so it stopped at "fixture mkdir failed".  The fixture now
goes in the temporary folder (TEMP or TMP on Windows, TMPDIR
elsewhere, else /tmp), made with path_mkdir(), and is removed with
rmdir /s /q on Windows.
2026-11-04 19:30:43 +01:00
Davey HughesandLibretroAdmin 08e5b72a88 Scan from the command line: exit status, Manual Scan options, playlist refresh (#19977)
* file_path: stop at the end of the path after '~'

fill_pathname_expand_special() skipped two characters after a leading
'~' or ':', so a bare "~" read past its NUL and "~x" lost the x. It
now skips the prefix and a slash after it, if there is one.

* scan: hand the callback user data and what the scan did

task_push_dbscan() and task_push_manual_content_scan() take user data
for their callback, and the callback's task_data is now a
task_dbscan_result_t: how many entries the scan added, how many its
playlists already held, and how many files matched nothing. A file is
counted by what became of it, so an archive that is expanded is not
counted as unmatched, and a loose scan's unmatched files count as
added. Callers pass NULL for now.

* scan: say in the result when a scan failed

A scan that ends before it runs (bad setup, no valid content, a DAT
that will not load) or ends with a task error now sets the result's
failed flag. A database folder that is missing or unset is treated as
an empty one, so it reports the same "no database" error instead of
finishing as if it had scanned.

* scan: count the files a scan could not read

A file whose scan fails (an unreadable file, a failed lookup) was in
no count. The result now carries an errors count for them. They do
not fail the scan.

* scan: print --scan errors on stderr and totals on stdout

A command-line scan now ends with a totals line on stdout, or with the
reason it failed on stderr. A file it could not read is listed on
stderr. The ++/==/?? marks are coloured only when their stream is a
terminal, so pipes and redirected output get plain text; on Windows
redirected output keeps its marks instead of losing them to
WriteConsole.

* scan: the manual scan finder skips a finished task

A finished scan stays in the task queue while its callback runs, and
the finder that refuses a second scan for the same playlist still
matched it, so a scan pushed from that callback was refused.

* retroarch: run every --scan, then exit with what the scans did

Each --scan is collected while the command line is parsed and run in
turn once it is done, so later options and further --scan flags are
no longer dropped. A path is expanded and made absolute first; one
that does not exist, or a scan that cannot start, is reported on
stderr and the next path still runs. --scan with --cloudsync or
--command is refused.

Without --menu the exit status is 1 if any scan failed, else 0 if any
added an entry, else 2, and a content path given with the scans is
ignored, with a line on stderr. With --menu each finished scan starts
the next, as a second scan is refused while one runs.

* manual_content_scan: clear the database name a refresh did not save

A playlist with no saved database name left the name buffer
uninitialised, and the refresh compared it against every installed
database, so whatever was on the stack could pick a database the
playlist never named.

* scan: Skip Database References names the scan's one playlist

The option was meant to put the playlist's name in each entry's
db_name, even for a database match (#18641), for users who build
their own thumbnail sets. When the scan picks the playlists, db_name
also picks the playlist an entry goes to, so with the option on every
match went to one playlist named after the content folder.

It now applies only when the scan writes one playlist, and gives
that playlist's name, a loose scan's unmatched entries included. With
the target on <Automatic>, or Fully Automatic with the option left on
from a Custom scan, matches go to their databases' playlists as with
it off; the entries of a custom playlist look their thumbnails up
under its name, not the folder's.

* scan: name the scanned folder in the start line

The "Scanning content" line, in the log and in --scan's output, has
named the database folder since the manual scan and the database scan
were merged (2fdf736d51); before, it named the path being scanned. It
names the content folder again.

* manual_content_scan: reset, scan overrides and their apply

The command line's scan options need each scan to start from the
defaults and to lay their values over them, or over a playlist's saved
settings. Add a reset, a record of those options and a function that
applies it through the existing setters: a core named by path, file
name or display name, a database, a playlist, the database check or a
DAT, extensions and the switches, stopping with a reason at the first
that does not resolve.

A core path is normalised before it is compared. A core's file name
matches in the case given first, then ignoring case, on every
platform; a display name matches exactly. Given another database, a
playlist named after its database keeps that name as a custom one, so
a refresh still writes the same playlist.

The core setter's copy step becomes its own function, so a core found
by path or file name can be set. Nothing calls the new functions yet.

* scan: run --scan items through one chain

The --scan paths ran through a loop in retroarch.c, and with --menu
through a second chain of their own. The scan options and playlist
refreshes to come need one runner for every item, from the command
line and from the menu.

task_push_dbscan_chain() runs a list of items one task at a time: a
prep task lists the databases, then each step is pushed from the last
task's callback on the main thread, so nothing waits and nothing
locks. An item that fails is reported and the run goes on; a
cancelled task ends it. retroarch.c builds the items in the first
argument pass and hands them to the chain; without --menu it waits
for the chain's end and exits as before.

Running out of memory prints "out of memory" on stderr: for an item's
path the item fails, and for the chain itself nothing runs, which
without --menu exits 1.

The chain also prints a scan's override errors, and the warnings for
an option that needs one playlist or does nothing for a single file,
which the scan options of the next commit can trigger.

* retroarch: Manual Scan options for --scan

--scan-playlist, --scan-database, --scan-match, --scan-dat, --scan-core,
--scan-exts and the five switches set the Manual Scan settings of the
--scan before them; a --scan without any stays Fully Automatic.

Values are checked in the first pass, so an option before any --scan,
an empty or bad value, or one too long for its setting ends the run
with its reason on stderr and status 1, before the config loads and
before anything is scanned. Cores, databases and DAT files are
resolved when their scan runs, and one that does not resolve fails
only that scan.

* scan: refresh playlists in the chain

A refresh item rescans one playlist with its saved scan settings, as
the menu's Refresh Playlist does, with the item's scan options laid
over them and written back by the scan. A refresh-all item does the
same for every playlist in the playlist folder that has saved
settings, in the order Manage Playlists lists them, histories and
favorites left out.

A refresh item names its playlist by name or by a path in the playlist
folder. A bare "name.lpl" is the playlist folder's file, and an
absolute path is normalised before it is checked against the folder.
The name is looked up in the folder's listing on every platform, the
exact name first, then ignoring case, since macOS and FAT/exFAT cards
ignore case; the refresh then takes the file's own name, so a
database's playlist stays one. Two or more playlists that match only
in other cases fail the item.

The playlist folder walk and each playlist load run in prep tasks: on
the worker with a threaded queue, under the shared I/O window without
one, each pushed from the previous step's callback. A playlist whose
saved content folder is gone is skipped rather than failed. A listing
that runs out of memory fails the refresh-all; a playlist folder that
is missing or cannot be read lists nothing, and the refresh-all ends
with "no playlists with saved scan settings". A refresh's start line
names its content folder; its totals and failure lines name the
playlist.

* retroarch: --scan-refresh and --scan-refresh-all

--scan-refresh=NAME|PATH rescans a playlist with its saved scan
settings, as the menu's Refresh Playlist does, and --scan-refresh-all
rescans every playlist that has them. Both are items of the same run
as --scan: the scan options after one are laid over the saved
settings and written back, and the exit status follows the same rule.

--scan-playlist after a refresh is refused, so a refresh always
updates its own playlist, and an empty --scan-refresh is refused as
the other empty values are. Like --scan, the refresh options cannot be
combined with --cloudsync or --command; the error names whichever of
the three comes first on the command line.

* menu: Refresh All Playlists

Manage Playlists gets Refresh All Playlists as its first entry when it
lists any playlist, so an empty list still says there are no entries.
It rescans every playlist that has saved scan settings, with those
settings, through the same chain as --scan-refresh-all, and ends with
one notification saying how many playlists were refreshed, skipped
(content folder gone) and failed.

While a content scan runs it is refused with a new message; a refused
scan had none. Other languages come through Crowdin.

While a scan chain runs (Refresh All, or a command-line scan with
--menu), Start Scan, a playlist's Refresh, Scan Directory and Scan
File are refused with the same message, as they would run with the
settings the chain last loaded. task_push_dbscan() returns false then
too, which covers the companion and the iOS caller.

* docs: --scan, refresh and the Manual Scan options in the man page

--help lists the options but not what a script needs: the order the
scans run in, the exit status and what the options set. The man page
now has them, next to --cloudsync, and CHANGES.md has the entry.

* scan chain: writes from workers reach the playlist cache by note

A playlist written by a task - the chain's prep parse, a scan, a
playlist-manager task, whatever thread it runs on - leaves a note on a
lock-free stack instead of reading the cached playlist; the main
thread applies the notes when it next decides whether the cache may
stand in, so no worker reads playlist_cached while the main thread
frees or installs it. Without threads or pointer atomics a write
applies at once.

The chain's prep tasks set their handlers by name, and
thread_read_audit.py fails on a handler set from a
retro_task_handler_t parameter, which would hide every handler passed
in. The chain puts the Manual Scan settings it found back when it
ends, and is no longer running when its done callback runs, so that
callback may start another. task_push_dbscan() says why it refuses,
so the menu and the companions show the same reason, and the
companions' status line takes it from companion_core. --scan and
--scan-refresh set the verbosity override, so a config with logging
off cannot detach the console the totals go to, and refuse a path too
long for the chain's buffers. The Refresh All notification passes the
length written, and fill_pathname_expand_special() stops at the end of
a buffer shorter than the home or application directory.

Lanes: playlist_parity_test writes the cached file from a worker,
once at the same size and then while the main thread reuses and frees
the cache; database_scan_test checks the refusal message, the restored
settings and a done callback that starts the next chain;
fill_pathname_test expands into a buffer shorter than the home
directory.

* playlist: header-only parse for the scan chain's prep

The prep task needs a playlist's saved scan settings and nothing else,
yet parsed every entry and, through autofix, could write the file back
from a worker. playlist_parse_begin_header() stops at "items", rebases
the scan record in memory when the base content directory moved, and
marks the playlist so playlist_write_file() refuses it.

Lane: playlist_parity_test header_only (entries skipped, scan dir
rebased, file bytes untouched, old format).

* scan chain: ask the file system nothing on the main thread

The chain's steps ran on the main thread between its tasks and stat'd
and realpath'd there: a --scan path's resolve and stat, a
--scan-refresh path's two realpaths, the content folder's stat, the
saved and override DATs' stat and size, an override core path's
realpath.  On a network share each of those can stall a frame.

- manual_content_scan_fs_read() reads them on any thread, touching
  nothing but its arguments; the _fs variants of the content-folder,
  playlist and override setters take its answers instead of asking.
  The plain setters pass NULL and behave as before, so the menu is
  unchanged.
- A --scan path gets its own prep task (dbscan_path_handler); a
  --scan-refresh resolves its path inside the load task, and the load
  reads the folder, the saved DAT and the overrides' paths once the
  header is parsed.  The worker gets copies of the item's strings.
- The prep's state is freed in one place.

Lane: database_scan_test "fs off main": six items that each stop
before their scan, on a threaded queue, with path_stat, path_is_valid,
path_is_directory, path_get_size and path_resolve_realpath wrapped:
no call on the main thread.

Not moved: the scan task's own push still stats the content folder
and the DAT (manual_content_scan_get_task_config), shared with the
menu.

* samples: the scan chain's lanes hold on Windows and without realpath

From a native Windows run of both tests:

- database_scan_test "fs off main" refreshed f_gone.lpl through
  "pl/../pl/", which only a realpath folds; the sample builds without
  RARCH_INTERNAL, where path_resolve_realpath() folds nothing, so the
  item was rightly not in the playlist folder.  It names the playlist
  by its plain path now, which the check still realpaths on the
  worker.
- "another case, a database's playlist" read the system name type
  after the chain ended, and the chain puts the Manual Scan settings
  back then.  The item callback records each scan's type instead.
- playlist_parity_test's header lane expected "/moved/snes"; autofix
  converts the suffix to the local form, "/moved\snes" on Windows.
- The worker lane's stress read required a core name of every read.
  Windows replaces the file with MoveFileExW, and an open that meets
  the replace fails: that read is an empty playlist, which has none.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-11-04 19:34:05 +01:00
LibretroAdmin fb1595d62d samples: link on MinGW again
verbosity.c mirrors each log line into the desktop companion under
HAVE_COMPANION_WIMP, which ui_companion_driver.h turns on for every
desktop Windows build, so each sample that links verbosity.c failed
to link there: undefined ui_companion_driver_log_active and
ui_companion_driver_log_msg.  samples/ui_companion_log_stub.c gives
the six of them a companion that is never open.

The database sample's main.c used strlcpy() without <compat/strl.h>,
an error with GCC 14 and later.
2026-11-04 19:05:04 +01:00
Anthony Schemel fd880c6ac0 s3: sort query parameters as SigV4 requires; sign nothing out of memory
s3_canonicalize_query_string splits the query on '&', sorts the
parameters by name and then by value in byte order, and joins them
again; names are compared on their own, so a name that extends
another sorts after it. Callers keep URL-encoding each name and value.

When the canonical headers or query cannot be allocated the signer
returns no signature, which s3_build_auth_header_ex passes on as NULL
and every caller treats as a failure.

s3_backup_test checks the sort order, including names that extend
one another, and fails each of the signer's allocations in turn.

Closes #19956
2026-11-04 17:51:42 +01:00
Anthony Schemel b82437f038 webdav: no Digest header without an accepted challenge
The cnonce is written only when a challenge is accepted and cleared
with the rest of the digest, so webdav_create_digest_auth_header()
builds nothing while it is empty: after a challenge the parser
rejected, the next request goes out with no Authorization header and
the server's 401 is reported as usual.

webdav_reauth_test sends rejected challenges (an open-ended nonce or
realm, no nonce, qop="auth-int") after an accepted one and checks
that no header is built from them.

Closes #19954
2026-11-04 17:44:31 +01:00
Davey Hughes 9e1b758786 video: a GPU index chosen in the menu replaces the remembered device (#19947)
Since 0e7a56f171 each GPU index remembers the device it was chosen as,
but a change made in the menu left the old name saved. On the next
driver init the resolver found that name at its old position and went
back to it, so the new choice was ignored, and on EGL contexts the menu
value reverted too. Left, Right, the dropdown and reset now save the
name of the device at the chosen index, and index 0 clears it. An index
past the end of the list leaves the saved name alone. Left no longer
sets index -1 on an empty device list.
2026-11-04 17:44:31 +01:00
Davey HughesandLibretroAdmin 188f37a97b nfs, smb: apply setting changes without a restart, log why a listing failed (#19982)
* nfs, smb: apply menu setting changes without a restart

The clients took their settings at startup and after config_load
only. Their numbers (NFS version, ports, timeout, read-ahead,
connection count, SMB auth mode) are copies, so a menu change waited
for a restart. Each change now hands the clients the current values.

SMB keys its pools on the whole configuration, so its next connection
uses the new values. The NFS pool matched a free connection by export
alone, so a changed server, version or port could ride an old one.
New settings now close the idle connections and advance a generation;
a slot whose generation or server differs connects again. The pool is
kept rather than replaced, so nfs_init_cfg() cannot free it under a
thread that has just taken a slot.

On version 4 an NFS port of 0 means 2049 and the mount port is
unused; the sublabels say so.

vfs_threads_test changes the ports while a prefetcher holds a slot.

* nfs, smb: log why a network directory listing failed

A failed file browser listing of an nfs:// or smb:// directory showed
"Directory not found" and logged nothing. It now logs one error line
with the URL, the settings the client ran with and the client's
reason.

The NFS client's reasons say what failed: the step and the server's
status (v4.2 mount: LOOKUP roms: NFS4ERR_NOENT), the address a connect
was refused at or timed out on, and RPC rejects such as PROG_MISMATCH
or AUTH_TOOWEAK. A refusal (PERM, ACCESS, WRONGSEC, or a v3 MOUNT
ACCES) adds that a Linux export needs 'insecure' for a client port
above 1023, as this client's always is. A failed walk or redial keeps
its own reason.

Failures that never reach a server record one too: no export, no
server in the URL, no SMB server set. SMB gets smb_get_last_error() to
match nfs_get_last_error().

nfs_error_test checks the reasons without a server and against
fake_nfs42.py.

* nfs: open files keep the settings they were opened with

An open nfs:// file or directory stored only its server and export, so
after a settings change its next read reconnected with the new version
and ports: a v3 handle sent over v4, or the stream's server asked on
another server's port.

Open files and directories now keep the version, ports and timeout
they were opened with and reconnect with those. Each pool slot carries
the parameters it connected with and is reused only on an exact match,
which replaces the settings generation. Read-ahead is client-side and
still follows the settings on every take.

nfs_error_test reads an open file on across a port and version change.

* smb: list a server's shares when no share is set

Browsing smb://server with no share configured never listed anything.
retro_vfs_opendir_impl rejected any SMB handle whose dir was NULL,
which is exactly the handle retro_vfs_opendir_smb returns for a share
list, and leaked it. The backend already reads and closes that handle,
so the dispatcher now accepts it. The UWP dispatcher does the same and
takes an entry's directory flag from the listing instead of a stat.

vfs_test checks the share list through the dispatcher as well.

* nfs: a file opened over a private connection keeps its export

With no NFS Export set, the export comes from the URL and is found when
connecting. When every pooled connection is busy, as it is once a few
read-only streams' prefetchers hold one each, the open goes over a
private connection and the file or directory handle recorded an empty
export. Every later borrow then mounted "": refused on v3; on v4 the
pseudo root, where the path under the export is not found, so such a
stream never got its prefetcher.

Record the export the connection was made with, through a new
rnfs_get_export().

nfs_error_test opens a file by URL while a prefetcher holds the only
slot.

* nfs, smb: settings as immutable copies; pools keyed by the copy

nfs_init_cfg() and smb_init_cfg() copy the settings they are given,
strings included, and publish the copy through vfs_cfg_snapshot.h:
a reader holds the copy it got between enter and leave, and a copy
that was replaced is freed once nothing holds it - retro_retire.h's
epochs where the pointer atomics are lock-free, a reference count
under a short flag elsewhere. Menu edits may rewrite the settings in
place while pool, prefetch and task threads read them.

NFS resolves a URL against the held copy's export without copying it,
and applies the timeout and read-ahead on every take: neither is
part of what a connection must match. SMB pools remember the copy
they were last matched to, so a take on that copy and share finds its
pool without building or comparing a key. A READDIR failure names the
result's own status.

nfs_cfg_race_test and smb_cfg_race_test free the caller's struct right
after handing it over, then resolve against it, and hand one struct
over repeatedly while four threads stat and list; they need no server
and run ahead of the server matrices in local_unfs3_test.sh and
local_samba_test.sh.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-11-04 17:39:23 +01:00
LibretroAdmin 688cc53eda font: keep the cells frames still on the GPU draw from
A glyph cell looked up in the current frame is not handed to another
codepoint until the frame is over. Where the CPU writes cells into
memory the GPU samples - the RSX's atlas in texture memory, the
textures GXM and GX2 copy cells into - the frames before may still be
drawing from them, and a cell reused for a new glyph shows up in their
place. A consumer now says how many earlier frames that can be
(font_atlas.inflight): the RSX one, GXM as many as swaps may be
pending, GX2 the frame swapped last, and a cell any of them looked up
waits until they are over. The default, zero, is the rule as it was.

font_frame_test fills the cache in one frame with two in flight: the
next two frames take none of its cells, and the third finds every cell
free and the first frame's glyph where it was.
2026-11-04 17:33:48 +01:00
LibretroAdmin 61c298b7fb CI: give container jobs explicit DNS servers
Container jobs resolve through the runner host's Docker network, and
when that upstream resolver flakes the checkout step fails with
"Could not resolve host: github.com" regardless of the image. Pass
--dns 1.1.1.1 --dns 8.8.8.8 so the job network's embedded resolver
forwards to public resolvers instead.
2026-11-04 17:18:53 +01:00
Davey HughesandLibretroAdmin 6a59a4772b NFS/SMB: reach shares from content scan, fix Parent Directory on network paths (#19990)
* file_path: path_parent_dir stops at a scheme:// root

The slashes of "scheme://" were taken for directory separators, so
going up from "nfs://host/" gave "nfs://", then "nfs:/", "./" and the
local working directory. The menu's Parent Directory fell off a
network share into the local cwd, and path_mkdir() on a missing share
path recursed down to a local mkdir("nfs:/").

A scheme root now returns "", as "/" and "C:\" do. path_basedir() and
paths without a trailing slash are unchanged, so "cdrom://drive1.cue"
still has the parent "cdrom://".

* menu_displaylist: offer the configured NFS server as a file browser root

The root of every file and directory browser lists the local volumes and
the configured SMB share, but not the NFS server. Scan Directory, Scan
File, Content Scan's Content Location and the directory settings could
not reach nfs:// at all; only Load Content's Browse NFS Export could, and
it offers no way to scan or pick a folder.

The root now gains the nfs:// entry from menu_displaylist_build_nfs_root()
when an NFS server is set, the same way the SMB share appears.

* menu: Parent Directory stops at the configured NFS or SMB root

Going up from the configured network root could still reach levels that
cannot be browsed. With the NFS export given in the server address
(nfs://host/mnt/user/roms), every level above the export shows "Directory
Not Found". With an SMB share set, smb://srv/ lists the share's root
again, and every folder opened from there lists that root again.

Parent Directory now goes to the drive list from the root the settings
describe, without the subdir: nfs:// plus the server address, or smb://
plus the server and, when a share is set, any one folder after it, since
the SMB VFS opens the configured share whatever that folder is called.
The subdir remains a start folder that can be left. Other paths keep
stopping at scheme://authority/.

* menu: Content Location offers the same places as Load Content

Content Scan's Content Location opened its folder picker at the start
directory, so a configured NFS export or SMB share was only reachable
by climbing Parent Directory up to the drive list.

It now opens Load Content's list of places first, built by the same
code: Start Directory, Downloaded Content, the drives, Browse SMB Share
and Browse NFS Export, without the two settings shortcuts. Each opens
the folder picker at its own root. When a location is stored, the list
starts with it, and that entry reopens the picker in the folder that
holds it, with it selected, as Content Location did before. A stored
NFS or SMB root reopens at the root itself, since the level above it
cannot be browsed.

* menu: show the NFS export in file browser titles

With an export set in the NFS settings, the browse root is nfs:// plus
the server and the VFS resolves paths against the export, so the title
showed the server and folders but never the export. Titles of paths
under the configured server now show it after the server:
nfs://server/export/folder.

Only the title text changes. Stored paths, list entries, other hosts
and an export given in the server address stay as they were.

* file_path: a scheme is only a path's first component; NFS root lanes

path_parent_dir takes "://" for a scheme only when no separator comes
before it, so a local folder whose name holds "://" keeps its parent.
path_parent_dir_test pins that next to the scheme roots.

load_content_filter_test gains the configured NFS server: its root
with the export in the server address and set apart, the levels
Parent Directory skips, titles with the export after the server, and
the root's entry in a file browser's drive list.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-11-04 17:15:21 +01:00
Acts1631andLibretroAdmin 4e51bd75ed net_smb2: bound directory entry lengths without overflow (#19973)
* net_smb2: bound directory entry lengths without overflow

A server-controlled FileNameLength could wrap the addition used to
validate a QUERY_DIRECTORY entry. The UTF-16 decoder would then read
past the directory reply buffer.

Compare the length by subtraction and validate nonzero next-entry
offsets against the complete current entry and remaining reply.

* net_smb2: wrap-proof NTLM target info span, U+FFFD for lone surrogates

The NTLM challenge's target-info offset and length are checked by
rsmb_span_ok, which cannot wrap a 32-bit size_t. rsmb_utf8 takes a
low surrogate only when one follows its high surrogate and writes
U+FFFD for either half on its own, so names from the server are
always valid UTF-8.

samples/network/smb_retro/smb_dir_bounds_test hands rsmb_readdir
hostile directory pages placed at the end of their allocations
(wrapping and oversized name lengths, next offsets inside an entry
or past the page) alongside a well-formed one, and checks the name
decoder and span check; local_samba_test.sh runs it ahead of the
server matrix.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-11-04 16:59:49 +01:00
Acts1631andLibretroAdmin a8e46dccca net_nfs3: stop bitmap walks after truncated replies (#19974)
* net_nfs3: stop bitmap walks after truncated replies

NFSv4 bitmap word counts come from the server. Once an XDR cursor
failed, two loops kept decrementing the declared count, allowing a
short reply to consume billions of iterations.

Stop both walks on cursor failure, matching the existing reclaim path.

* net_nfs3: bound every reply read to the bytes received

Fixed-size fields in replies (stateids, verifiers, the session id)
are read through xdr_get_fixed and xdr_skip, so the cursor never
passes the end of a reply; nfs4_res reports a cut-short result as
NFS4ERR_BADXDR rather than NFS4_OK. OPEN results share one parser
for create and reclaim and READDIR pages one taker for v3 and v4,
each leaving the caller's state alone for a truncated reply, and
xdr_get_opaque checks a length and its pad apart so neither wraps
on 32-bit targets.

samples/network/nfs_retro/nfs_reply_bounds_test drives these
decoders on short and hostile replies placed at the end of their
allocations; local_unfs3_test.sh runs it ahead of the server
matrix.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-11-04 16:54:01 +01:00
LibretroAdmin 7d99aeb7c3 AGENTS.md: how to add to the regression tests
Where a new step goes and when it gets a job of its own, claiming a
new sample and regenerating the paths filters, the hand-picked source
lists harnesses link, and keeping tests to what the code does rather
than what the current runner image happens to do.
2026-11-04 16:30:17 +01:00
LibretroAdmin 58106ef61c CI: skip the workflows a push's samples and workflow files do not touch
Every push ran every workflow. tools/ci_paths.py now writes each
workflow's push and pull_request paths filter: everything, less the
samples trees and the other workflow files, plus the sample
directories its jobs build or run (through the scripts they call and
the paths those directories name in turn) and the workflow itself. A
push to one sample runs the full-tree builds no longer, only the
workflows that run that sample; a push to one workflow runs it alone;
any other change runs everything as before. Jobs that call a tool or
CI script, walk the samples trees or read the workflow files keep
those whole, and workflows with a filter of their own keep it.

The CI local runner workflow runs tools/ci_paths.py --check, which
fails when a filter no longer matches what its workflow names, and
tools/ci_local.py which reads past a generated filter to the coverage.
2026-11-04 16:30:17 +01:00
LibretroAdmin 9d368e7145 CI: split samples-gfx and the thumbnail preview into parallel jobs
samples-gfx ran about 150 steps one after another, and the thumbnail
preview job built and ran ASan and then TSan. The steps now run in five
jobs (drivers and widgets; Win32, X11 and display servers; D3D under
Wine; video and recording under Wine; KMS, Wayland, fonts and media),
each keeping its steps in their order, and the TSan half of the
thumbnail preview runs in thumbnail-preview-tsan, making the fixtures
it reads that 'make fixtures' does not.
2026-11-04 16:30:17 +01:00
LibretroAdmin a4838c9cb1 CI: put wineserver on the PATH where Wine's packages do not
Wine 10's Ubuntu packages keep wineserver in Wine's library directory
with nothing on the PATH, and the jobs start and stop it by name: the
network clients' Windows builds stopped at 'wineserver: command not
found', and every 'wineserver -k' and 'wineserver -w' elsewhere
quietly did nothing. apt-install.sh links it into /usr/local/bin when
it finds it there and not on the PATH.
2026-11-04 16:28:14 +01:00
LibretroAdmin b1194186ac Fix Wii U, dingux and aarch64 build warnings
Wii U get_arguments() prints through the argc/argv pointers it was
handed, and argv[1] only when there is one. The in-place URL decode
passes the string's real size to string_percent_decode(), and Basic
auth decodes its own buffer with the length it already has, so LTO
no longer sees a SIZE_MAX bound wrapping around.

Helpers only some configurations call are compiled for those only:
the overlay-only input and menu helpers under HAVE_OVERLAY, the
container-only audio_transfer time and movie-header helpers under
their container, and the scalar xxh3 long path off NEON. hidraw
hands autoconfig the stored vid/pid, rcheevos copies its operand by
assignment, and the Qt companion asks QScreen from Qt 5.10 on.
2026-11-04 15:53:34 +01:00
LibretroAdmin f08faaa0a6 Samples: audio timeline gaps and overlaps reach the encoder in its frames
The gap and overlap fixtures cut timestamps with aselect and asetpts.
ffmpeg 8 regroups the filter's audio into encoder frames and gives each
the timestamp of its first sample, so on ubuntu-latest the gap fixture
came out with no gap and the timeline test found the sine where the
seek expects silence. Those fixtures' source frames are now exactly
the encoder's (960 samples for Opus, 64 for Vorbis), which ffmpeg
hands on as they are, timestamps included.
2026-11-04 15:53:20 +01:00
LibretroAdmin f377429db6 win32: look AttachConsole up at runtime
The log console attaches to the parent's console through AttachConsole,
which is Windows 2000 and later: mingw-w64 13 declares it only for
_WIN32_WINNT 0x0500 and up, so the NT 4.0 build (the Visual C++ 6
project's) no longer compiled, and on older Windows the import kept
the executable from loading. It is resolved from kernel32 the way
GetConsoleWindow is, and without it a console of our own is opened.
2026-11-04 15:21:20 +01:00
LibretroAdmin bb0b67a4a8 Samples: AV1 fixtures tag the frames with their colour too
Built with the ffmpeg on ubuntu-latest, the 8-bit 4:2:0 stream's
sequence header carried BT.709 matrix coefficients but unspecified
primaries and transfer (S 192 128 8 2 2 1 0), where ffmpeg 6.1 and 7.0
write all three from the output options. testsrc2's frames carry no
colour, and the newer ffmpeg appears to take what the frame says over
those options. setparams tags the frames with what the options say, so
the encoder is told BT.709 either way. Unchanged with ffmpeg 6.1.
2026-11-04 15:03:27 +01:00
LibretroAdmin fb575b0608 Samples: subtitle_load expects an embedded load pending only unthreaded
embedded() asserted a load it had just queued was still pending, which
a worker thread can already have finished: one run in three failed with
clang. run_mode() makes the same check only without threads; embedded()
does too.
2026-11-04 15:03:27 +01:00
LibretroAdmin 410593e901 rsubtitle_srt: split a whole WebVTT line out of vtt_lines_step()
Unoptimised under ASan and UBSan, vtt_lines_step()'s frame was 2040
bytes with clang 18 and 2056 with the clang on ubuntu-latest, past the
sample's 2048 limit. A whole line's handling is its own function now;
the larger of the two is about 1500 bytes.
2026-11-04 15:03:27 +01:00
LibretroAdmin 0d62aafd61 Makefile: build the PipeWire objects as gnu99 in the C89 lane
The SPA headers on ubuntu-latest now declare in for loops
(spa/pod/compare.h, spa/param/audio/layout-types.h), which -std=c89
rejects outright; reading them as system headers only lifts -pedantic.
The three PipeWire objects take -std=gnu99 after the lane's -std=c89,
and keep -Werror=declaration-after-statement for their own code.
2026-11-04 15:03:27 +01:00
LibretroAdmin c4b8107200 vulkan: name the validation message in the log again
Validation layers before 1.4 began each message with its ID,
"Validation Error: [ VUID-... ] ...". Later ones (1.4.341 on
ubuntu-latest) hand the ID over only as pMessageIdName, so the debug
callback logged an error with no name, and every harness keyed on the
ID or on "Validation Error" stopped seeing it: vulkan_validation's
acquire test, expecting MissingAcquireWait, failed; threaded_video,
vulkan_hdr_ui_pass and vulkan_resize, counting "Validation Error",
would have passed with errors. The ID is put back in front, in the old
shape, when the message does not already carry it.
2026-11-04 15:03:27 +01:00
LibretroAdmin 281b532e55 Samples: rsx_alloc links font_atlas_place()
rsx_font_init() keeps the atlas in texture memory through the font
driver's font_atlas_place() since b0353a6439, and the harness, which
compiles rsx_gfx.c without font_driver.c, no longer linked. It now has
the placement the font driver makes, and its renderer hands the buffer
back the way the font driver's release does, so the in-place path is
the one font_sweep fails allocation by allocation.
2026-11-04 15:03:27 +01:00
Codex 7ac012b7be retro_atomic: use native MSVC x86 acquire and release accesses
Use aligned native-word loads and stores with compiler barriers on modern MSVC x86/x64, avoiding locked read-modify-write operations for acquire/release access. Evaluate store arguments before the barrier. Keep 64-bit accesses on 32-bit x86 and other architectures on their existing paths, and retain the full sequentially consistent fence.

Exercise all available widths and single evaluation. Checked MSVC x64/x86 and volatile:iso, MinGW, Linux ASan/UBSan, native eventcount stress and full parallel-xbox360 builds/lifecycle fixtures. MSVC assembly uses native moves; the pinned synthetic load loop measures about 0.185 ns/op versus 3.66 ns/op for compare-exchange, without implying dependent-load latency or application speedup.
2026-11-04 14:45:14 +01:00
Codex 4740703a42 retro_atomic: keep MSVC fences independent of Windows macros
Vulkan-Hpp undefines MemoryBarrier, so including retro_atomic.h afterwards fails in the sequentially consistent fallback load, and later fence use can also fail. Express the full fence with InterlockedExchange on a local word and retain ARM ordering barriers. Test Windows macro removal before and after header inclusion.

Checked native MSVC x64/x86, MinGW, Linux ASan/UBSan and emitted AArch64 seqlock barriers.
2026-11-04 13:54:24 +01:00
LibretroAdmin a3e9cc4a70 net_http: request identity content coding
net_http hands the body to callers as sent and decodes no content
coding, so every request now says so with Accept-Encoding: identity;
without the header a server is free to pick any coding. The resume
harness checks both the first request and the resumed one carry it.
2026-11-04 13:33:13 +01:00
LibretroAdmin d4dd09526e d3d11: keep the font's coverage in alpha on devices without A8
A device that samples no A8_UNORM - some of feature level 9 - is given
an RGBA8 font texture rather than whatever the format list offers past
A8, none of which keeps the coverage in the alpha the font shader
reads; each texel staged or handed to UpdateSubresource is white with
the coverage as its alpha, the latter through a scratch the font keeps.

The font_atlas_upload lane makes the texture on a device that samples
no A8 and sends two cells far apart into RGBA8 by map and while the
staging texture is the GPU's, and covers D3D10's rectangles the same
way.
2026-11-04 11:32:13 +01:00
LibretroAdmin b0353a6439 rsx: rasterize font glyphs straight into the texture
The font texture is linear B8 with its pitch its width, the atlas's
own layout, so the atlas is placed in texture memory and is the
texture: the glyph cache rasterizes into it and nothing is copied. A
grown atlas is the texture as it is; the memory it left is retired
until the font goes. Without texture memory for it the atlas stays on
the heap and is copied in as before.
2026-11-04 11:17:51 +01:00
LibretroAdmin 3bba27a5be font: keep an atlas in storage the consumer samples in place
font_atlas_place() moves an atlas's samples into storage the consumer
makes - texture memory it samples them from as they are - and the
glyph cache keeps them there as the atlas grows, each buffer it moves
out of handed to the consumer's storage_free, the last when the font
goes. The font_lifecycle frame test refuses the storage, then places
the atlas, grows it there and frees the font.
2026-11-04 11:17:51 +01:00
LibretroAdmin 4f8d348026 gl1, gl2: convert atlas uploads in a scratch the font keeps
The LUMINANCE_ALPHA texels an upload is converted into go in one
scratch buffer per font, grown to the largest upload asked for and
kept, which a grown atlas's respecify is made in before the texture
takes the new size. The gl2_pbo_readback lane respecifies and uploads
a region with every allocation refused once the scratch is made.
2026-11-04 11:17:51 +01:00
LibretroAdmin 89869d5ea6 d3d12: stage font regions as rectangles into mapped upload buffers
Each of the font texture's two upload buffers is mapped once and stays
mapped while the texture has it (d3d12_font_map), let go of before the
texture is (d3d12_font_unmap). The atlas's dirty rectangles are staged
on their own and copied in with a boxed copy each, between one pair of
barriers. The d3d12_frame_ring lane counts the buffer's maps across a
frame's strings and stages two cells far apart as two regions.
2026-11-04 11:17:51 +01:00
LibretroAdmin 78364a1618 gfx: send dirty atlas cells as rectangles in more font drivers
The glyph cache keeps the cells drawn into since the last upload as up
to FONT_ATLAS_DIRTY_MAX rectangles. Vulkan, D3D10, D3D11, Metal, GX2
and GXM now send each one rather than their bounding box: Vulkan as
one copy with a region and a flush range per rectangle, D3D10 and
D3D11 through one map of the staging texture and a boxed copy each,
Metal as a blit each, GX2 and GXM as rows of each, GX2 flushing just
those. Vulkan keeps what it has staged since its last upload with
font_dirty_rects_add(), which the cache's own list now uses.

The font_atlas_upload lane sends two cells in opposite corners on GX2,
GXM and D3D11 (by map and while the staging texture is the GPU's)
into textures holding a fill value everywhere else.
2026-11-04 11:17:51 +01:00
LibretroAdmin 168c451a6b sdl2, gdi: send a glyph cached mid-line before drawing it
A left-aligned line is drawn without being measured first, so a glyph
it caches lands in the atlas after the line has begun. As sdl3 does,
the SDL2 raster font sends what it has batched and uploads the atlas
when a lookup dirties it, and GDI updates its atlas mirror then,
deselected while it may be made again. The SDL2 raster font's unused
atlas_dirty flag is removed. The font_lifecycle sdl2 test draws a line
that caches a glyph partway through.
2026-11-04 05:25:40 +01:00
LibretroAdmin bb89ddd7f8 gl1, gl2: take a grown atlas's size only with its texels in hand
The raster font's texture takes the grown atlas's size when the buffer
of texels for the respecify has been made, and keeps the size it has
while it cannot be (gl1/gl2_raster_font_follow_atlas); a region past
the texture goes in clamped to it, and the atlas stays dirty while a
region cannot be sent. gl2 sets a zero row length for the upload where
it has one. The gl2_pbo_readback harness grows an atlas with the
allocation refused, then allowed.
2026-11-04 05:25:40 +01:00
LibretroAdmin cde56da824 metal: copy font atlas cells in through the frame's blit
The atlas texture is its own, filled from the CPU only while no command
buffer names it. Cells the glyph cache rewrites are staged in the
frame's buffer ring and copied in by the blit command buffer the frame
commits ahead of its drawing, which orders the copy after the frames
still in flight that sample the texture, clamped to the texture as
well as the atlas. A grown atlas whose texture cannot be made keeps
the old texture, and font init fails without one.
2026-11-04 05:25:40 +01:00
LibretroAdmin 75e518f77d d3d11: keep the font texture when a grown one cannot be made
The font texture is made again at the atlas's grown size beside the
old one, which is let go only once the new one is whole; when it
cannot be made the old one stays, and the atlas region is clamped to
the texture as well as the atlas. The font_atlas_upload lane makes the
texture again on a device whose texture creation fails, and then on one
where it succeeds.
2026-11-04 05:25:40 +01:00
LibretroAdmin 8b0967ef7d gx2, gxm, rsx: bound font atlas uploads by the texture
An atlas whose texture could not be made again at its grown size is
larger than the texture it has: the dirty region goes into it clamped
to the texture's own width and height, row by row, so the cells the
texture holds keep being uploaded and nothing lands past it
(gx2_font_upload_dirty, gxm_font_upload_dirty, rsx_font_copy_dirty).
GX2 flushes a whole sprite from the CPU's cache for each glyph drawn
(gx2_font_invalidate_sprites).

The font_atlas_upload lane runs the three uploads, extracted from the
drivers, into textures allocated exactly their size, under ASan and
under mingw-w64 with Wine, and checks the size GX2 flushes.
2026-11-04 05:25:40 +01:00
LibretroAdmin 8ee796235c omap: draw nothing of a glyph clipped off the page
A glyph wholly off the page's top or left edge is clipped to no rows
or columns and is not blended, and the room left on the page is taken
after the clip. The font_lifecycle omap lane blends FreeType's glyphs,
whose boxes are their ink, from the shipping driver into a page exactly
its pixels in size, with the message at the top, where '-' lies wholly
above the page.
2026-11-04 05:25:40 +01:00
LibretroAdmin 42021c0f5a d3d12: stage font glyphs over this frame's own copy without waiting
A font copy recorded in the frame being recorded reads the upload
buffer only once that frame is submitted, and no cell drawn in a frame
is staged again in it, so a later string bringing new glyphs in the
same frame stages over that copy's source in place, waiting for
nothing (d3d12_font_stage_dirty).

The atlas region is clamped to the texture as well as the atlas, and a
grown atlas whose texture cannot be made keeps the old texture. The
d3d12_frame_ring lane stages three strings into one frame with one and
two frames in flight, where a wait for a fence not yet signalled
fails, and an atlas larger than its texture.
2026-11-04 05:25:40 +01:00
Davey HughesandLibretroAdmin 464adda0ee vulkan: keep staging flushes valid on non-coherent memory (#19986)
* vulkan: map staging memory whole for whole-size flushes

Texture and staging memory is flushed or invalidated with VK_WHOLE_SIZE
at the initial upload in vulkan_create_texture, the font atlas, the
frame and streamed textures and the readback buffers, but was mapped
only up to the texture's size. A whole-size range must end on a
nonCoherentAtomSize boundary or at the end of the allocation
(VUID-VkMappedMemoryRange-size-01389), which a size such as a 1x1 RGBA
image's 4 bytes in a 16-byte allocation does not. Only memory that is
not host-coherent is flushed, so it shows mostly on mobile drivers.

Mapping to VK_WHOLE_SIZE makes the mapping end where the allocation
does, as it already ends in the menu texture and recording readback
paths.

* vulkan: skip the font atlas flush when no copy follows

When the atlas grows and the textures cannot be made again at its
size, needs_update stays set while the dirty box is empty, its start
at the atlas's size. Every frame the flush was then aimed at the end of
the staging memory or past it (VUID-VkMappedMemoryRange-size-00686),
though the copy that follows, bounded by the staging texture, sent
nothing.

Flush only when the dirty box starts inside the staging texture, the
same bound the copy uses.

* vulkan: map texture memory from the allocation's start

A linear image's texels may start past offset 0 of its memory, while
the whole-size flushes and invalidates on that memory are issued from
offset 0, which has to lie inside the mapping. VULKAN_MAP_TEXTURE maps
the whole allocation and points past the subresource offset, as the
menu upload path already does; the frame, software framebuffer and
initial-upload textures use it. The initial upload skips its copy when
the map fails.

* vulkan: bound the font atlas flush and copy by one box

The dirty box is clamped to the staging texture once, and both the
flush and the copy use it: an empty box, including one left past the
textures after the atlas grew and they could not follow, does neither,
and the flushed range always covers what the copy reads.

* vulkan: drop the old texture memory reuse in vulkan_create_texture

The old texture is parked on the deferred list, which clears it, before
memory is chosen, so it never has memory to hand over or to free here.
The reuse branch and both cleanups that followed it are removed.

* samples/gfx/threaded_video: run the Vulkan lanes on non-coherent memory

fake_rebar_layer gains a unified-memory layout whose cached
host-visible type is not coherent and whose allocations are whole
pages (FAKE_REBAR_NONCOHERENT), and binds linear images past offset 0
of their memory (FAKE_REBAR_IMAGE_PAD), so the driver's hand-written
flushes and invalidates run under the validation layer.
check-vulkan-noncoherent runs the lanes with both, for RGB565 and
XRGB8888 cores, and holds that CPU-written textures and linear frame
images land there; the Vulkan validation workflow runs it.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-11-04 04:40:18 +01:00
github-actions 9b5829702e Fetch translations from Crowdin 2026-11-04 00:16:47 +00:00
Codex f13b1459b1 libco: preserve context state and streamline 64-bit switches
Preserve AMD64 floating-point controls and Windows stack metadata, and
AArch64 callee-saved SIMD registers and floating-point state. Reject
allocation-size overflow and enter new AArch64 contexts through a return
trap.

Keep Windows switch code in read-only executable image sections. Publish
Unix fallback initialization atomically, reduce context storage and use a
direct resume branch on SysV AMD64. Private allocation hooks allow cores
to retain guarded-stack ownership without changing the public API.

Add ABI register and independent FP-mode regressions for Linux AMD64,
Windows AMD64 and AArch64, and run the native sample in common CI.
2026-11-04 00:34:22 +01:00
Codex 7d8c433ba5 rxml: safely release partial parser allocations
A failed accumulator or frame allocation can leave an uninitialized document allocated. Free the independent allocations directly before tree initialization, rather than traversing its uninitialized members. Exercise owned one-shot and incremental parsing with poisoned allocations and each allocation failure.
2026-11-03 22:43:47 +01:00
Codex 73713c6d62 md5: read unaligned input without aliasing violations
Load little-endian bytes explicitly on architectures that previously dereferenced unaligned uint32_t pointers. Cover unaligned whole-block and fragmented updates against a known digest and verify that input is unchanged.
2026-11-03 22:43:47 +01:00
Codex 5187dbe697 hash: process SHA-256 blocks directly and enable MSVC SHA intrinsics
Borrow complete input blocks, dispatch once per batch and use a 16-word scalar schedule. Keep modern MSVC SHA instructions behind the existing runtime feature probe. Cover streaming boundaries, unaligned input, SHA-224 and raw block processing.
2026-11-03 22:43:40 +01:00
LibretroAdmin f3316c77d8 Samples: discrete_multichannel in a quarter of the time
The test took 49 s, nearly all of it in two places that test nothing:
- each of the hundreds of audio re-inits built the sinc resampler's
  tables from scratch;
- eight scheduler cases call a consumer pass with nothing queued,
  which waits out the frontend's one-second idle bound each time.

The driver's resamplers now come from a cache in the test: one parent
per configuration, kept for the run, and the driver gets a sibling of
it -- its own fresh stream over the parent's tables, freed as usual. A
backend without siblings is made the usual way. And the test sets the
idle bound to 20 ms, far past anything a case waits for; the driver
keeps its one second unless a build defines AUDIO_PIPE_WAIT_MAX_US.

49 -> 12 s, 20 s under ASan and UBSan, every case passing.
2026-11-03 19:01:27 +01:00
LibretroAdmin 39ed0e4fd6 Samples: pipeline_rate_control's default run at its own length under sanitizers
Under ASan and TSan the default run was doubled to 32 s, so the sink
estimate would still settle with more of its windows left out. All
eight runs of the CI step go at once, so these two set its length;
the rest take 12 to 16 s.

At 16 s under both sanitizers, eight runs at once on two cores, three
rounds: the estimate applies two or three times, settled every time,
and the mean is within 22 ppm of the 500 allowed. With two CPU hogs
on top both lengths drift alike, 387 ppm at 16 s and 401 at 32, so
the longer run bought no margin. The doubling is gone.

The CI step 36 -> 23 s with its two builds.
2026-11-03 19:01:22 +01:00
LibretroAdmin 0ef3b08d45 Audio: fit() for the nearest and CC resamplers
A flush into a lent span fills it straight from the resampler only as
far as fit() says is sure to fit; without one it sizes from the ratio's
bound, which leaves a few frames of the span to the copy at the wrap.
Only sinc had a fit.

Both now run process()'s own clock without the samples: nearest steps
its fraction, CC its distance, in the same float operations, so the
answer is exact. Every CC arm, C, SSE and NEON, moves distance the
same way, so one model covers them; a CC stream made for downsampling
and run above a ratio of one is moved to the upsampler from its start,
as process() does. Builds whose float arithmetic is not repeatable --
x87 extended precision, fast-math -- and the PSP's VFPU arm have no
fit and keep the bound.

lend_write's fit case now runs all three backends, nearest and CC made
at one ratio and run at eight others, and also asks before any input
has been processed: 2296 fits exact. Mutations of each fit's step,
threshold and the CC switch are each caught.
2026-11-03 19:01:15 +01:00
LibretroAdmin 39a6ae0120 CI: run the native audio transport checks' runs at once
Linux-audio-transport took twelve minutes a matrix entry, most of it
waiting: the wide formats (32 runs, 220 s), the tempo extremes and live
controls (20 runs, 150 s), the speed-linked low-pass (8 runs, 84 s) and
the lend through the real wrapper (3 runs of a second each, 53 s). Each
run is a stretch of audio against a clocked device on next to no CPU,
run one after another in shell loops and recipes.

Each run is its own make target now, and a check runs its runs under a
sub-make with PAR (-j16, each run's output kept together); PAR= gives
the old order. The live set is one list the layout, speed-lpf, runloop
and auto checks all run with their own environment, check-wide runs
every format through both layouts and the live set on each at once, and
the lend, pause, menu, consumer-exit and kept-device checks run their
runs the same way.

On two cores: check-wide 220 -> 16 s, check-tempo 150 -> 8 s,
check-speed-lpf 84 -> 11 s, check-lend 53 -> 18 s, the same under
ASan within a few seconds; the whole job 1459 -> 234 s for both matrix
entries here, every check run twice without a failure.
2026-11-03 19:01:11 +01:00
LibretroAdmin a4c7689a6c rzstd: keep short match copies disjoint on builds without vectors
A match copied with slack went to the wide copy once the source was
two vector widths back. The wide copy's short case is one fixed
32-byte memcpy, and on builds with no SSE2 or NEON a vector is eight
bytes, so a match 16 to 31 bytes back and 32 or fewer long was copied
over itself: undefined, aborted by the sanitizer and by fortified
libcs, and liable to repeat stale bytes. The wide copy now also needs
the source 32 bytes back; below that the eight-byte path, which takes
any offset of eight or more, does it. On SSE2 and NEON both limits are
32, so nothing changes there.

The raw block test is also built with the vector path switched off
(rzstd_raw_block_test_novec, run in the samples job); it fails without
this change and passes with it.

The overlap-copy check flagged rzstd_match_copy's call sites since the
helper copies within one buffer; it is now audited and on the allow
list.
2026-11-03 18:32:40 +01:00
LibretroAdmin 176686d34e Samples: stub video_driver_hw_context_shared_note for the GL2 tests
gl2.c now tells video_driver.c whether a hardware core's context is
shared (video_driver_hw_context_shared_note()), and the samples that
build gl2.c against the stand-ins in gl2_pbo_readback/stubs.c -
gl2_pbo_readback, gl_last_pass_fbo and gl2_kms_frames - stopped
linking on the undefined reference. The stand-in takes the note and
does nothing with it, as the tests run no hardware core.
2026-11-03 17:42:48 +01:00
Codex da0d927cf2 rzstd: copy terminal matches in bounded words
Grow short periods before word copies and keep the final tail narrow. Cover offsets 1 through 32 and exact output boundaries in the existing regression.
2026-11-03 16:56:47 +01:00
Codex 45076e1fb4 rzstd: allow reuse of frame decoder workspace
Provide a single-owner decoder for callers processing independent frames into their own output buffers. Keep the one-shot entry point, and test frame resets and recovery after truncated or undersized output.
2026-11-03 16:56:47 +01:00
Codex 05d99ded8c rzstd: publish BMI2 support with retro_atomic
Make lazy capability detection race-free without a constructor or a blocking initializer.
2026-11-03 16:56:47 +01:00
Codex 8b85cfdf3b rzstd: mask frame magic bytes before narrowing
Keep emitted bytes unchanged while avoiding constant-truncation warnings in strict MSVC C89 builds.
2026-11-03 16:56:47 +01:00
LibretroAdmin 57fc8ce875 Audio: fill the span before the wrap exactly, from the resampler's clock
The flush in parts left one copy at the driver's wrap: the input given
to the first span was sized from the output bound, which keeps a margin
of sixteen frames and one more over the ratio, so the resampler's
output fell some twenty-five frames short of the span and that sliver
was filled from the frontend's buffer.

The sinc resampler can say exactly what the next process() makes of
each input frame: a frame is pushed while the phase clock is a phase
or more, a frame comes out at each step of the integer ratio until it
is again, and nothing else enters into it on the nominal path. The
resampler interface gains an optional fit(): the most input frames
whose process() is sure to produce no more than the frames asked for,
exact for the stream's state where the backend can tell, 0 where it
cannot (the decimated fast-forward path, or a backend without one, and
the caller sizes from the bound as before). The flush sizes the first
span's input from it, so the span is filled to within the frames one
input frame makes - a frame or two at 1.5x, often none at all - and the
copy left at the wrap is that or nothing. The bypass fills its span
exactly as it is, a frame in being a frame out.

retro_resampler_t's new member is last, so a backend that does not set
it is NULL there; libretro-common is RetroArch's, for the cores to
sync.

lend_write_test checks the fit is exact: for every quality, with and
without HQ oversampling, at ratios above and below one, after runs
that leave the stream at any state, the input fit() names makes no
more than asked and one frame more makes more. Off by one either way
it fails; a fit that overshoots also publishes past the span, which
the device stand-in refuses.
2026-11-03 16:02:32 +01:00
libretroadmin 3662cb40c3 wgl: keep a cached core-profile context instead of making it again
When a restart keeps the WGL contexts the core is told so and not
reset, but a core-profile or debug context - glcore always, gl when the
core asks for 3.1 or later - was then created again over the kept one.
The core went on with objects the new context does not have, and the
kept shared context leaked. A kept context is now used as it is.
wgl_context_keep.py runs create_gl_context() against stubbed WGL calls.
2026-11-03 15:07:36 +01:00
libretroadmin 23f465bf2d runloop: hold the content rate where Driver Pacing has no backend
With Driver Pacing on and nothing else holding the loop, the backend's
release keeps the content's frame period. On a driver without a backend
- gl, glcore, or a GPU without the extension - the setting did nothing,
and with vsync and audio sync off the loop ran unthrottled. The frame
limiter now holds the content's period in exactly the iterations a
backend would have owned: a content frame at normal speed, focused, the
menu down, and nothing else pacing. Driver Pacing off is unchanged.
2026-11-03 14:55:28 +01:00
libretroadmin 8ca610dc6e gl: reset a cached-context core that moves to the other context
A core that asks for its context to be kept is not reset across a video
restart when the context driver kept its contexts. The gl driver gives a
hardware core either its own context or a second one shared with it -
always the shared one under the threaded wrapper - and framebuffer and
vertex array objects are not shared between the two. Toggling threaded
video moved the core from one to the other without a context_reset, and
its next draw used objects that do not exist there: an access violation
inside the GL driver on WGL, which keeps both contexts.

The gl driver now reports which context the core renders in, and when
that changes across a kept-context restart the acknowledgement is
withdrawn, so the core is reset into the context it now has.
gl_context_move.py checks the decision and its wiring.
2026-11-03 14:55:21 +01:00
LibretroAdmin 7fb9eea29d Audio: resample across the driver's wrap, the flush in parts
Where the driver's ring wrapped inside a flush, a float stereo device
still took that flush the old way: the lend came back short and was
abandoned, the resampler wrote the frontend's buffer, and write()
copied every frame into the ring - about one flush in sixteen on a
typical ring, and the last copy the lend left on that path.

The flush now runs its output stage in parts. The resampler is given
as much input as is sure to fit the short span (audio_driver_input_
bound), writes it there carrying its state, and the span is published.
Its output falls a little short of the bound, so a sliver of a few
frames is left before the wrap: enough input to fill it is produced in
the frontend's buffer, the sliver copied from there - the one small
copy left - and the frames over are carried to the head of the next
span, where the rest of the flush is produced past the wrap. The
mixer, the clamp, the resume ramp and the pause mute run on each part
in turn, each carrying its state, so the stream is the one the whole
flush made: the lend test compares it bit for bit. The bypass's copy
splits the same way. An int16 device keeps narrowing into its spans
on the way out; a device that lends nothing, or no room after a
bounded wait, takes the staged write as before.

The produce, the finish and the sink accounting are their own
functions now, run once per part; audio_driver_input_bound() is built
for every configuration.

lend_write_test's rings wrap 1000 frames into a 1200-frame flush, and
one wraps twice: a float stereo device resampled or at unity, threaded
and inline, must give the written stream bit for bit with write()
never called. A ring that cuts a frame at its end still sends what is
left through write(). Dropping the carry, not shifting it, or letting
the fit overshoot the span each fail it.
2026-11-03 14:51:41 +01:00
LibretroAdmin 34e276c35c Audio: make the device frames span by span where the driver's ring wraps
A lend asks the driver for one span to hold a whole flush. Where the
driver's ring wraps inside it the span comes back short, the lend is
abandoned, and the flush is produced in the frontend's buffers and
copied in by write() - about one flush in sixteen on a typical ring.

The resampler's output cannot be split there, but every stage after it
makes the device's frames one at a time, its state carried from call
to call: the narrowing of a float mix for an int16 stereo device, the
upmix for a wider one (int16, float, or float narrowed for an int16
device, the core's own surround channels merged over it), and the
binaural render on headphones. audio_driver_stage_spans() runs such a
stage into as many spans as the driver lends - both sides of the wrap
- in whole frames; audio_driver_stage_write() takes the rest, a frame
the wrap would cut or what has no room, into the stage's own buffer at
the offset the whole would have had, so a short threaded write's tail
is still where audio_driver_retain_output() looks for it, and writes
it. The upmix and binaural lend, which took one span or none, is this
now; a float stereo device's resampler output still goes through
write() at the wrap. The extras merge takes the first frame of the
span it works on.

lend_write_test gains a device whose ring wraps inside the span, of
whole frames and of a size that cuts a frame at its end: int16 and
float stereo, 5.1 float and int16, headphones float and int16, a core
sending its own 5.1, threaded and inline. Each stream must equal the
written one bit for bit, with write() never called where the ring
holds whole frames. On the previous audio_driver.c those cases call
write(); a merge that ignores the span's first frame fails the 5.1
core's.
2026-11-03 14:23:47 +01:00
LibretroAdmin d09c384983 Microphone: write the core's samples straight into the outgoing ring
Every microphone flush ended the same way: its last stage - the
narrowing of float samples, or the left channel of the integer
resampler's stereo - produced the samples in final_frames, and
retro_spsc_write() then copied them into the ring the core reads.

microphone_enqueue() takes the ring's span at the head, in the one
span or two its wrap leaves, and has the stage write the samples
there: the float fast path, the integer resampler's left channel and
the float resampler's narrowing all reach the ring without the stop in
between. final_frames is gone from the arena and from
microphone_driver_state_t; the resampler's output bound is the smaller
of the two buffers still on the way. An int16 device at its rate keeps
its single copy, from what the driver read.

The copy it saves on an 800-sample flush, a 60 Hz frame at 48 kHz:
0.03 us, and an intermediate buffer of the largest flush.

mic_handshake_test gains a lane that puts the ring's head at every
place it can be and enqueues runs of every length up to the whole
ring, with both fills: the ring must hold exactly what the stage used
to produce on its own, and report the samples it took. Writing only
the first span, or taking the right channel, fails it.
2026-11-03 14:03:47 +01:00
LibretroAdmin 1b7b9818fe Audio: lend the driver's buffer to the upmix and the binaural render
The lend of the driver's buffer stopped at stereo: on a wider device
the upmix produced the device frames in upmix_buf (upmix_i16 for an
int16 device) and write() copied them into the driver's buffer, and
with Headphone Virtual Surround on the binaural render did the same
into its own two-channel buffer.

audio_driver_write_frames() now borrows the span for the frames the
widening stage is about to produce - the upmix, its int16 form, the
narrowing of a float mix for an int16 device, or the binaural render -
and has the stage write them there, the core's own surround channels
merged in place as before, then publishes them. Inline and blocking it
waits for the room first, as write() would have; a span cut short, or
no room after the wait, leaves the stage writing its own buffer and
write() taking it as it always has.

The copy it saves, each 60 Hz flush into a 64 ms device: 0.28 us for
5.1 float, 0.13 us for 5.1 int16, 0.09 / 0.05 us on headphones - and
the pass over the stage's own buffer before it.

lend_write_test runs a 5.1 device and Headphone Virtual Surround,
float and int16, resampled and rate-matched, threaded and inline,
checks the stage under test is the one running, and compares the lent
stream bit for bit with the written one: write() is never called,
and a driver cutting spans short still gets the same stream through
write(). On the previous audio_driver.c the wider cases each call
write().
2026-11-03 14:03:44 +01:00
LibretroAdmin 1460bf0e49 CI: run the sinc variants and the paced lane's latencies at once
The slowest steps left in Linux-samples-audio:

- sinc fast-forward, 40 s: an ASan build and run, an AVX one, then a
  MinGW one under wine, one after another, each run on one thread.
  Each is now built to its own binary and the three run at once.
- sinc sibling, 20 s: its ASan and MinGW runs, likewise.
- the fill cap lane, 36 s: its audio_paced lane runs three device
  latencies of four settings each in real time. PACED_ONLY takes
  audio_paced_16, _32 or _64 for one latency, so the step runs the
  three beside the other four lanes; an unknown one is refused.

Measured on two cores: sinc fast-forward 40 -> 21-24 s, sinc sibling
20 -> 15 s, the fill cap lane 36 -> 24-27 s, each three runs in a row
or more without a failure.
2026-11-03 02:54:21 +01:00
LibretroAdmin 7050e1cda2 CI: run wasapi_exclusive_pacing's scenarios at once
wasapi_exclusive_pacing_test was the slowest step left in
Linux-samples-audio, 87 s: twelve scenarios of a fake device on the
wall clock, three seconds each and again with retries, then the clock
and bitstream cases - once under ASan and once under TSan, one after
another, on next to no CPU.

PACING_SCENARIO=<n> now runs scenario n alone, PACING_SCENARIO=rest the
cases after the scenarios, and PACING_SCENARIO=count prints how many
scenarios there are, so the step runs every one under both sanitizers
at once through run-parallel.sh and picks up a scenario added later on
its own. A number past the last one is refused rather than passing on
nothing. Unset, the test runs everything as before.

On two cores the runs take 8 s instead of 86 s, with no scenario
needing a retry in three runs in a row.
2026-11-03 02:28:57 +01:00
LibretroAdmin 342b224021 Samples: read the XAudio2 room after the lend, not before it
xaudio_lend_test failed on GitHub's runner with "a lend is within the
free room and the allocation". The test read write_avail() and then
called write_begin(), and checked the lend against the earlier
reading - but XAudio2's thread frees buffers whenever it finishes one,
so a lend taken a moment later can rightly find more room than was
read before it. A runner slow enough between the two calls failed
the check with the driver doing nothing wrong.

The room is now read right after the lend. Only the test's thread
takes room, so it can only have grown since the lend measured it, and
a lend past it is still a lend past the room. With a 3 ms sleep put
between the two calls, the old check fails on every run and the new
one passes.
2026-11-03 02:15:52 +01:00
LibretroAdmin 8360f25509 CI: run the slow audio samples' real-time lanes at once
Three steps of Linux-samples-audio took six minutes between them, and
two of them spent it waiting: pipeline_rate_control's eight runs and
pipeline_clocked's fill cap lanes are seconds of audio each against a
device that drains in real time, on almost no CPU, run one after
another.

.github/workflows/scripts/run-parallel.sh starts commands at once and,
when all are done, prints each one's output in order with a pass or
FAIL line, failing the step if any failed. The rate control step builds
its ASan and TSan binaries in turn and runs all eight at once; the fill
cap step runs its five lanes at once, pipeline_clocked_test taking
PACED_ONLY=<lane> to run one (PACED_ONLY=1 still runs them all).
discrete_multichannel_test is CPU-bound - most of it building Kaiser
tables - so it is now built at -O1 instead of -O0, and its plain and
ASan runs share the runner's cores.

Measured on two cores: pipeline_rate_control 162 -> 33 s of runs, the
fill cap lanes 116 -> 36 s, discrete_multichannel 87 -> 40 s. Each new
arrangement passed three times in a row there.
2026-11-03 01:59:17 +01:00
LibretroAdmin fff1632fac XAudio2: lend the frontend the buffers it plays
The XAudio2 driver had no write_begin / write_end, so every flush's
output was produced in the frontend's own buffer and then copied by
xa_write() into the buffers XAudio2 plays - the copy WASAPI, DirectSound
and the other lending drivers no longer make.

The driver's sixteen buffers are cut from one allocation, so the free
ones after the head are one span up to the allocation's end.
xa_write_begin() lends that span, never more than the free room, and
xa_write_end() moves the head over what was published, submitting each
buffer it fills. The span stops where the free room does, so every
buffer it fills has a slot and write_end never waits. A span cut short
at the allocation's end leaves the frontend's staged write to wrap, as
for the other drivers. The submit is shared with xa_write().

samples/audio/xaudio_lend runs the shipping driver on wine's XAudio2
(Windows' own on Windows): lends stay within the free room and the
allocation, lends and writes make one stream, write_end publishes all
it is given at once even filling the whole room, an abandoned lend
takes nothing, and lent audio plays at the pace written audio does.
Lending past the room hangs it; a write_end that does not move the
head fails it. Linux-samples-audio runs it.
2026-11-03 01:28:00 +01:00
github-actions 4eecb8ef4e Fetch translations from Crowdin 2026-11-03 00:14:53 +00:00
LibretroAdmin ee4525c5ae Replay: keep a checkpoint's buffers from one checkpoint to the next
A replay records or plays back a checkpoint every few seconds, in the
middle of the game, and each took fresh state-sized buffers: the
statestream encoding (half again the state), the compressed copy -
calloc'd, zeroed only to be overwritten by the compressor - and on
playback the compressed copy read from the file and a calloc'd
buffer to decode into, then copied into cur_save for a raw state.

The handle now keeps the encoding and compression buffers
(bsv_movie_scratch()), grown only when a state outgrows them, never
zeroed, freed with the movie. A raw state is inflated straight into
cur_save, so its copy is gone too. A zlib checkpoint whose buffer
could not be taken now fails instead of compressing into NULL, and a
checkpoint that fails part way marks cur_save as holding no state.

The buffers a zstd checkpoint of a raw state takes and touches, per
checkpoint (the codec's own work aside): 4 MiB 0.81 -> 0.41 ms,
16 MiB 2.66 -> 1.35 ms, 64 MiB 45.5 -> 9.0 ms.

bsv_statestream_test gains a lane that records and plays back eight
checkpoints, raw and statestream, plain and zstd, each decoding to the
bytes written, with nothing state-sized of the replay's own allocated
after the first two (what zstd allocates inside a call is measured
through rzstd directly and allowed for). It fails on the previous
bsvmovie.c. bsv_checkpoint_bounds_test's copy of
bsv_movie_load_checkpoint() follows the shipping one again, with
bsv_movie_set_end() and the size checks it had fallen behind on.
2026-11-03 00:58:21 +01:00
LibretroAdmin f9c77d8e80 Savestates: keep the replaced file for undo as it is, not inflated
A save over a slot first reads the file there into the undo buffer,
so Undo Save State can put it back. That read went through rzip: on
every save over a compressed slot the old state was inflated in full,
and kept inflated in memory for the rest of the session. Undo then
wrote it back through rzip, deflated afresh, compressed or not as the
setting had since become rather than as the file was.

The backup is now the file's own bytes, read plainly, and undo writes
them back as they are. Nothing is inflated on a save, nothing deflated
on undo, the buffer held for undo is the file's size rather than the
state's, and the slot comes back byte for byte.

An 8 MiB state that compresses to 4 MiB: 2.1 -> 0.6 ms for the backup
on every save over the slot, 12.4 -> 3.3 ms for the undo, and 4 MiB
less held for it. At 32 MiB: 9.9 -> 3.8 ms and 41.6 -> 25.3 ms.

save_state_io_test gains an RZIP=1 build with HAVE_COMPRESSION, as the
frontend ships it, where every lane also passes, and a lane that saves
over a slot with the compression setting turned in between, both ways,
then undoes: the slot must come back byte for byte. It fails on the
previous tree. Linux-samples-tasks runs that build under ASan.
2026-11-03 00:25:14 +01:00
LibretroAdmin f140e9592a Soft-patching: apply a streamed IPS patch to the loaded ROM where it lies
A streamed IPS patch built its target as the source arrived: a second
buffer the size of the ROM, taken at open, every byte of the source
copied into it, then the records laid over it, and the source freed.
The target of an IPS patch is the source with the records on it, and
the source is the content buffer the load already owns.

patch_stream_finish_into() finishes a stream whose source the caller
owns (malloc'd, given to patch_stream_set_source()): an IPS patch puts
its records on that buffer in place, growing it with realloc() only
when the target is longer, and any other format hands back its target
in the buffer's place. On failure the buffer is still the unpatched
source, as before. Fed in place, IPS copies nothing while the source
arrives; a chunk fed from elsewhere copies what came before through to
the target and carries on as before. The target is no longer taken at
open but by the first copy-through, so the in-place path never takes
one. The content load finishes its patch with finish_into().

A 16 MiB ROM with a 2000-record IPS patch, first touch: 6.7 ms ->
0.05 ms to apply, and 16 MiB less heap during the load.

The patch_stream test finishes every in-place and fallback comparison
with finish_into() on a buffer of its own, and checks a 4 MiB IPS
source is patched where it lies without being moved or copied. Leaving
the grown tail unzeroed, skipping the records in place or in the
catch-up, copying nothing in the catch-up, or copying through while fed
in place each fail it.
2026-11-03 00:25:08 +01:00
LibretroAdmin 69d936201c Soft-patching: read the source in place while streaming BPS, UPS, xdelta
The content load streams a soft patch alongside the read: each span the
transfer fills is fed to the applier as it arrives. BPS and xdelta may
copy from anywhere in the source, so both kept their own growing copy of
every byte fed (BPS in src_buf, xdelta in own_src), and UPS kept the
part it had not consumed yet in its carry. The transfer buffer already
holds the whole source in one allocation that never moves, and stays
alive, detached as the content, until the patch is finished - so that
copy is a second ROM in memory, written once and read back.

patch_stream_set_source() (and vcdiff_stream_set_source() under it)
tells the stream where the source it is about to be fed lies. Chunks
fed from there are read in place: no copy, no realloc as it grows, no
memmove of the UPS carry. A chunk fed from anywhere else falls back to
the old copy, starting with what came before it, so a caller that does
not keep the source whole still gets the right answer. IPS keeps no
source and is unchanged. content_file_pump_source() sets the transfer
buffer as the source.

A 32 MiB BPS hack patch: 32 MiB less heap during the load, and
143 -> 137 ms to apply.

The patch_stream test feeds each comparison as a copy, in place, or in
place then from another buffer (freed at once, so ASan catches a stream
still reading it), and checks with glibc's heap counters that a 4 MiB
BPS source and the xdelta source are no longer copied. Dropping the
bytes the fallback copies, restarting the UPS carry at 0, reading the
wrong byte in place, or ignoring the setter each fail it.
2026-11-03 00:25:03 +01:00
LibretroAdmin 2f98293e1c Audio: build the inline pipeline's lend without threads
The inline pipeline's lend of the driver's buffer sizes what it borrows
with audio_driver_output_bound(), which was defined only with
HAVE_THREADS (or the harness's AUDIO_OUTPUT_BOUND_CHECK): a build
without threads stopped at audio_driver.c on an implicit declaration,
caught by the compile matrix's no-threads lanes. The bound is defined
for every build now; its inverse, which only the threaded pipe uses,
stays behind the guard.
2026-11-03 00:25:00 +01:00
LibretroAdmin f0c0dea7d5 Savestates: move compressed states in whole rzip chunks
The save and load tasks moved a state in 100 KiB quanta, and rzip cuts
its stream into 128 KiB chunks. A write shorter than a chunk never met
rzipstream_write()'s direct path, so every byte was first copied into
the stream's buffer and compressed one chunk at a time, and the
parallel chunk compressor (two or more whole chunks in one write)
never ran for a savestate at all. A read always inflated each chunk
into the stream's own buffer and copied it out.

A compressed save now writes two whole chunks per quantum: they are
compressed straight from the state, across the worker pool. A
compressed load reads two whole chunks per quantum, and rzipstream_read()
now inflates a whole chunk wanted by the caller straight into the
caller's buffer, the read-side mirror of the write's direct path; a
chunk that would inflate past chunk_size is corrupt and fails as it
did. What a quantum moves to or from the device is the compressed
size, about half the state, so the slowest storage the quantum is
sized for is not asked for more per tick. Uncompressed states keep
the 100 KiB quantum, and Emscripten its 16 MiB one, already a multiple
of the chunk. RZIP_DEFAULT_CHUNK_SIZE moves to rzip_stream.h for
writers that want to meet it.

For a 16 MiB state on two cores, a compressed save goes from about
217 ms to 118 ms and the output is byte for byte the same; a load in
whole chunks goes from about 31.5 ms to 29.5 ms (best of six).

The rzip codec sample reads both codecs back in quanta from one byte
to past several chunks, around a rewind and a short last chunk, and
checks that 100 KiB and two-chunk writes make the same file.
2026-11-03 00:10:43 +01:00
LibretroAdmin 868cdc1718 Samples: stop three host-timing checks from flaking
Each failed on a loaded CI host while the code it checks was right.

asio_teardown judged whether the teardown wait ended as the last
callback left by a 100 ms wall-clock limit, and a busy host was late
to wake the waiter (170 ms seen). It now watches the driver's bounded
wait instead: a bound that runs out with no callback left inside is a
notify the wait slept through. The negative build, whose last callback
out does not notify, fails that way on every run however fast the host
is; a waiter that is merely woken late passes.

pipeline_clocked retried its shallow setpoint run when the host ran
the device dry or held the core, but not when the host took the
consumer off its core during the first second and drained it below the
setpoint, which then failed the startup check. That dip is retried as
well. Priming short of the setpoint does it in every run, and still
fails.

d3d11_turns' unfair core must be caught keeping the frontend waiting,
which only shows when the core is running on another CPU as the
frontend asks; on a host that had it off the CPU, the unfair core let
the frontend in at once and the must-fail run passed. An ask now
counts only when the core was seen taking turns just before it; a run
with too few such asks reports INCONCLUSIVE and is run again, and the
unfair core gets up to five runs to be caught.
2026-11-02 23:24:28 +01:00
LibretroAdmin 0501ee5d4b D3D11: lend software-rendering cores a framebuffer
D3D11 had no get_current_software_framebuffer, so every frame from a
software core was copied on the CPU, by dxgi_copy into the dynamic
sw_direct texture or into history[0]'s staging texture, while GL,
glcore, Vulkan, D3D12 and D3D9 all let the core render into memory
the GPU reads from.

The core is now lent one of three staging textures, mapped READ_WRITE
(host-cached memory, which cores may read back) with DO_NOT_WAIT, so a
slot the GPU is still copying from is passed over rather than waited
for; when none is free the core renders into its own buffer that one
frame. A frame pushed from a slot is unmapped and copied into
chain.history[0] with CopySubresourceRegion, and drawn from there as
any staged frame is, by the stock chain, a preset or views. The copy
on the GPU replaces the one on the CPU. The slot the last pushed frame
came from is not lent straight back, and each push retires the cached
frame, which points into memory the unmap takes away; a redraw draws
history[0] as it stands.

Lent only for unthreaded video (the threaded wrapper lends its own
slots), for XRGB8888 and RGB565 frames, while history[0] is at the
frame's size and in its format, and when the slot's row pitch is the
frame's tight pitch.

The new d3d_failure_paths sw_lend_gpu sample runs the shipping
functions on a real D3D11 device: 120 frames rendered into lent slots
reach history[0] through the GPU copy, the cached frame's slot is never
lent straight back, every push retires the cached frame, other sizes
and hardware cores are declined, and every slot goes unmapped. It runs
under Wine in Windows-wine-menu.
2026-11-02 23:24:24 +01:00
LibretroAdmin bc198f7611 Audio: borrow the driver's buffer on the inline pipeline too
With the threaded pipeline off, the flush always resampled into
output_samples_buf (or narrowed into output_samples_int16) and write()
then copied the whole chunk into the driver's ring, even though every
driver with a ring in front of the device - pulse, pipewire, alsa,
coreaudio, wasapi, sdl2, aaudio, opensl, jack, dsound, wdmks - lends
that ring through write_begin/write_end. Only the threaded consumer
took the lend.

The inline flush now takes it as well. A blocking device is first
waited on for the chunk's room through wait_writable(), the wait
write() would otherwise have made piece by piece. When the driver
cannot lend the whole chunk - the span is cut short by the ring's
wrap, or the bounded wait found too little room - the lend is
abandoned and the staged write runs and blocks as before. That saves
one copy of each flush's output, about 6.4 KB a frame for float
stereo at 48 kHz and 60 fps; the resampler still dominates the
flush's cost.

The lend write sample runs its four cases inline as well: the lent
stream is bit for bit the written one, write() is not called for a
resampled chunk, and every begin is balanced by an end.
2026-11-02 23:24:19 +01:00
LibretroAdmin bcf1062b4e Threaded video and audio: keep each thread's per-frame writes on its own lines
In thread_video_t the main thread's per-push writes shared cache lines
with fields the video thread writes every frame: core_time with
miss_run, drain_cooldown and drain_pending; last_time with the lent
slots' busy flags; the handoff counts and the published pacing values
with repeat_request, repeat_group and margin_extra_us. In
audio_driver_state_t the pipe's consumer (pipe_gen, pipe_stalled, the
underrun and priming state, the source wait counts) and producer
(pipe_data_gen, the runloop snapshot, pipe_ctrl_avail, writer_held,
the fast-forward multiplier) were interleaved over the same four
lines. Each write by one thread took the line back from the other.

The main thread's fields now sit together behind a line of padding,
with the values it publishes on a line of their own, the way the
struct already separates its other shared fields. The audio pipe's
consumer and producer fields form two groups, each between full lines
of padding, under a new AUDIO_PIPE_LINE. A full line on each side
keeps the separation wherever the fields land, without relying on
the allocation's alignment. It costs 184 bytes in thread_video_t and
192 in audio_driver_state_t.

The threaded video sample's line separation lane and the audio
pipeline wakeups sample assert the new separations by offset, so a
field added above a pad cannot quietly put them back on one line.
2026-11-02 23:24:14 +01:00
LibretroAdmin bca0f6cde4 Widgets: send a running task's progress without copying its text
Every frame each running task with a title went to the widgets with a
fresh copy of its title (and error) and a new command, only for the
widgets' owner to compare the title with the one it already had and
free it: a few allocations a frame per task, on the main thread,
during gameplay.

The task system now counts publications of a task's title and error
(task_get_text_gen()). The widgets record on the task what they last
sent it: the key, that count, and progress, flags, style and category
packed into one word. While the text is unchanged it is not copied,
and an update that changes nothing is not sent at all. A finished
task is always sent whole.

When the widgets' owner loses what a task sent it - a task widget
freed, an update dropped for lack of room, widgets cut loose for a
reinit - it bumps a static epoch, and every task sends its title
again. A widget handed on to another task is under a key that task
never sent, so its first update carries its title too. Where the
strings are read without the setters' publication (the runner without
lock-free atomics) the count never repeats and every update copies,
as before.

An unchanged push drops from about 30 ns to 11 ns, with no allocation.
The task bar sample checks that nothing is sent for an unchanged
task, that a progress step is sent once, and that a new title reaches
the widget; its title churn test now republishes the titles it
rewrites in place.
2026-11-02 23:24:10 +01:00
LibretroAdmin ec8e58ae45 Vulkan: record back-to-back layout transitions as one barrier
The retained-backbuffer copy and the OpenXR UI copy each moved two
images with two separate vkCmdPipelineBarrier calls before the copy
and two more after it. Each pair now goes in one call with both image
barriers and the union of the stage masks, halving the barrier calls
on those paths.

The pairs sit back to back with no work between them, so the union
waits on nothing the separate calls did not: an execution dependency
already covers every earlier command in its source stages, whichever
image it names.
2026-11-02 20:56:29 +01:00
LibretroAdmin 8a16cb4607 Vulkan: keep the pipeline cache on disk between runs
The driver's VkPipelineCache was created empty on every init and
thrown away on deinit, so the stock pipelines and every slang preset
pass were compiled from scratch by the driver on each launch, preset
load and driver reinit; only the SPIR-V was cached.

The cache blob is now stored through the shared shader cache in
<cache dir>/vkpipeline, one file per vendor/device ID. It is mapped
and handed to vkCreatePipelineCache where it lies, after the header
(version, vendor, device, pipelineCacheUUID) is checked so a blob from
another GPU or driver never reaches the driver; a rejected blob falls
back to an empty cache and is replaced on the next deinit.

Saving happens on deinit only, never while content runs, and is
skipped when the cache data is the size last read or written, so a
fullscreen toggle or driver reinit does not rewrite the file.
2026-11-02 20:56:26 +01:00
LibretroAdmin 68a0f1e485 Unix: build android_apply_device_defaults on Android only
The Vulkan 1.1 probe and android_apply_device_defaults() sat outside
any ANDROID guard, so every non-Android Unix build failed on JNIEnv,
jobject and g_android. Both callers and the prototype are already
Android-only.
2026-11-02 20:56:22 +01:00
LibretroAdmin 54d27c523a Android: read the display density from DisplayMetrics, not "wm density"
When ro.sf.lcd_density was empty, the display server ran "wm density"
through popen(), a shell child from inside the app.  That command
prints "Physical density: <n>", which atoi() read as 0, so the
fallback never produced a density and every such device got the
90 dpi guess; from an app the command is often refused anyway.

The fallback is now getResources().getDisplayMetrics().densityDpi, the
density Android itself scales by.  ro.sf.lcd_density is still read
first, so a device that reports it gets the same DPI as before, and the
90 dpi guess stays for a device that reports neither.

With this nothing calls system_property_get() any more, and it is
removed: the Android frontend no longer runs a shell to read anything.
2026-11-02 19:52:27 +01:00
LibretroAdmin 2b530acbac Android: apply the device's drivers on Reset to Default Configuration too
A fresh install picks Vulkan, AAudio and 64 ms when the device has
them, but "Reset to Default Configuration" went back to the built-in
OpenGL ES, OpenSL ES and 128 ms, the defaults the oldest devices need.

Reset now sets the same device-dependent values a fresh install gets.
The built-in defaults are still not changed, so a config saved with
"save minimal" keeps meaning what it did; the values are set as the
config's own and saved whatever the save mode.  A device whose Vulkan
driver fails is reset to Vulkan like any other, and the next start
falls back to OpenGL ES again.

android_apply_first_run_defaults() is now android_apply_device_defaults(),
declared in configuration.h instead of locally in configuration.c.
2026-11-02 19:52:24 +01:00
sonninnos 9c820ad77b Fix mouse grab constraint on fullscreen toggle 2026-11-02 20:29:49 +02:00
LibretroAdmin 821190cc1c Android: fall back to OpenGL ES when the Vulkan driver will not start
When the Vulkan driver failed to start, RetroArch exited.  On Android
the config still named Vulkan, so the app closed on every start until
its data was cleared.  With fresh installs now starting on Vulkan
whenever the device declares Vulkan 1.1, a declared driver that still
fails would do that from the first start.

On Android the frontend now switches to the "gl" driver in the same
launch, threaded or not, and saves the config so the next start does
not try Vulkan again.  A failed threaded start already puts the driver
back and leaves no thread, so the retry starts clean.  Not done for a
core that renders with Vulkan itself, which OpenGL ES cannot stand in
for; that still fails as before.
2026-11-02 19:01:07 +01:00
LibretroAdmin 3384df52dc Android: start a fresh install on Vulkan and AAudio when the device has them
A new Android install starts on OpenGL ES, OpenSL ES and 128 ms of
audio latency, which suits the oldest devices RetroArch runs on and
leaves current ones on the slower path.

On the first start, when no config file exists yet, the frontend now
picks:

- the Vulkan driver, when the device declares Vulkan 1.1 or later
  (PackageManager.hasSystemFeature("android.hardware.vulkan.version",
  0x00401000), what its driver passed conformance at).  This is read
  from the system, not by loading the driver.  A device with only a
  1.0 driver, or none, keeps OpenGL ES.
- the AAudio driver from Android 8.1 (API 27), with 64 ms of latency.
  8.0 has AAudio but with the bugs that make Oboe use OpenSL ES there;
  older releases keep OpenSL ES.  A device whose latency comes from a
  quirk (Xperia Play, GAMEMID_BT) keeps that latency.

Existing installs are not touched.  This runs only when there is no
config at all; a config that exists but will not open keeps the
built-in defaults.  And the built-in defaults stay what they were:
with "save minimal" a config leaves out every key that matches them,
so changing them would move those installs to new drivers on update.
The choices are set as the install's own values instead and saved
with the rest of its config, so they last whatever the save mode.
Each driver is only picked when the build has it.
2026-11-02 19:01:04 +01:00
LibretroAdmin 19328c8c7b Android: read system properties in-process, not through getprop
frontend_android_get_version_sdk(), frontend_android_get_name(), the
device-model reads in frontend_unix_get_env() and frontend_unix_init(),
and the display server's ro.sf.lcd_density read all went through
system_property_get(), which popen()s the getprop binary: a fork and
exec of a shell to read one property.  Several of these run on every
startup, and the SDK level is asked again by the input driver.

They now use __system_property_get(), which the frontend already uses
for the manufacturer and model and which every supported API level
has.  The "wm density" fallback is a command, not a property, and
still goes through system_property_get().
2026-11-02 19:01:00 +01:00
LibretroAdmin ca4be58216 retroarch: a save turned away gives back the slot it advanced
With Increment Save State Index on, Save State moves the slot up
before the save is pushed. A save the queue turned away - the saves
and loads held behind the one in flight already at their bound -
wrote nothing there, yet the slot stayed advanced, so the next Load
State named a slot with no file in it. command_event_main_state()
also called that save a success, and pruned old saves for it.

It returns false for a save turned away now, and prunes nothing for
it, and the slot goes back to where it was. A save that is pushed and
fails later on (a full disk) still leaves the slot advanced: later
saves may have moved it again by then.

The state-hold-full lane gains the case: with the hold full and
Increment Save State Index on, Save State must be refused and leave
the slot where it was. It fails on the old code.
2026-11-02 17:02:53 +01:00
LibretroAdmin 7715b745fe content_load_test: an unanswered command waits for its load, not 83 s
The content-reply lane checks that a line-based command interface is
not answered for LOAD_CONTENT. It asked through rr_ask(), which pumps
until a reply comes or 5000 frames pass - and as no reply should
come, it always waited the full 5000: 83 seconds at 60 Hz, two thirds
of the harness's run time, for a load that is through in a few
frames.

rr_ask_unanswered() waits for the load the command started to be
through, then a grace period of 30 frames for a reply that should not
come: one is sent from the frame the load finishes in. The lane goes
from 87 s to 4 s, and the harness from about 125 s to 40 s. The check
still bites: with the interface made structured, the reply is caught.
2026-11-02 17:02:53 +01:00
LibretroAdmin 3d525c2723 command: a Save or Load State turned away says so
A save or load pressed while another runs is held behind it, up to
four; past that bound it is turned away. Save State then logged
queued=NO and Load State returned false, and neither put anything on
screen: the hotkey did nothing, silently.

Both say so now - "Failed to save state to <file>." and "Failed to
load state." - through the message command_event_main_state() already
shows for its other outcomes.

A state-hold-full lane in the content_load harness fires six Load
States in the frame after a Save State: some are held, one past the
bound must say so on screen, and the held ones still finish. It fails
on the old code.
2026-11-02 16:51:58 +01:00
LibretroAdmin 738e87e81b task_core_backup: a backup or restore refused says so, or is taken
Two backups or restores of the same core may not run at once, and a
second one was refused without a word. It was also refused while the
first had finished but not yet retired - its callback still to run -
so Backup pressed again just after one completed did nothing either,
and the finder read the finished task's handle to decide.

A finished backup or restore is passed over now, before its handle is
read: its file work is done, and a second one is taken. One refused
because the same core's is still running puts "A backup or restore of
this core is already running" on screen. Sideload Core flushed the
menu to the top even when its restore was refused, leaving the user
somewhere else with nothing done; it flushes only for a restore that
started, as Restore Backup already did.

core_backup_io_test's findable lane now expects the second backup to
be taken while the first is finished but unretired, and a new lane
checks that a backup of a core whose backup is queued is refused with
a message on screen. Both fail on the old code.
2026-11-02 16:51:58 +01:00
LibretroAdmin fe1389662b http_transfer_stress_test: judge the drain per call, not as an average
The throughput guard asked for 256KiB per net_http_update() averaged
over the body. That is exactly net_http.c's drain budget, so it only
held when the test's server thread had a full budget waiting before
every call; on a loaded two-CPU runner it sometimes had not, and the
check failed about one run in four with the drain intact.

What the guard is for is that a call reads until the socket is empty
rather than taking one bite and returning. It checks that now, per
call, from the test's recv() hook: every body update must end on
EAGAIN, at the byte budget (within the minimum read window net_http.c
will not spend on a short read), or at the iteration cap. The average
is still printed.

Passed eight runs in a row, and six with three busy loops on the two
CPUs. With net_http.c cut to one recv() per call, every body update
is reported.
2026-11-02 16:51:57 +01:00
LibretroAdmin 54deb0ba7b task_menu_explore: a build the menu abandoned is not one in progress
When the menu goes, its Explore build is cancelled and left to wind
down; its callback then installs nothing. The finder still took it
for a build in progress, so Explore opened again before it had wound
down started no build of its own and stayed on "Initialising list"
until it was left and opened again.

A cancelled build is passed over now. The cancel flag tells, not the
task's handle, which may already be gone.

explore_build_test gains a reopen lane: a build pushed and abandoned
as the menu's teardown does, then Explore opened again, must end with
an index installed. It fails on the old code.
2026-11-02 16:51:57 +01:00
LibretroAdmin ada30c5de4 task_core_updater: a core picked during a Play Store install is held
Play Feature Delivery installs one core at a time, and a core picked
from the Core Downloader while another installed was turned away
without a word: the pick did nothing, however long the install took.

A pick from the menu is held now, in order, and started from the
running install's callback once it is through; the core it replaces
is unloaded then, as the install may delete its file. The same core
picked twice waits once, and past a bound of four a pick is refused
as before. The core switch keeps pushing its own installs one at a
time and moving on from one refused, and the callback leaves the held
picks waiting if the switch has pushed one meanwhile. Frame thread
only, where the menu pushes these and the callback runs: no lock.

pfd_switch_cores_test gains a lane, threaded and unthreaded: picks
made while the first install downloads are taken, run one at a time
and all install once it is through, and a repeated pick waits once.
It fails on the old code.
2026-11-02 16:51:57 +01:00
LibretroAdmin b143f37aa4 task_cloudsync: Sync Now and Resolve during a sync run after it
There is one cloud sync at a time, and a sync asked for while another
ran was turned away with a log line and nothing on screen. Content
loads and closes start network-bound syncs, so Sync Now, Resolve:
Keep Local and Resolve: Keep Server chosen during one did nothing.

A sync asked for without a pusher waiting on it is owed now, and
starts from the running sync's callback once it is through. Asked
for more than once, it runs once, with the conflict resolution asked
for last; a resolution wins over a plain sync. A pusher that waits on
its own sync is still told it was not run. The finder passes over a
finished sync, which stays in the queue while its callback runs, so
the follow-up started from there is not taken for a running one.
Frame thread only, where every push and the callback run: no lock.

cloudsync_pacing_test gains a follow-up lane: Keep Local and then
Sync Now during a sync are taken, keep the resolution, and one more
sync runs once the first is through. It fails on the old code.
2026-11-02 16:22:17 +01:00
LibretroAdmin a3cf738010 task_http: a GET retried from its own callback is not its duplicate
A finished task stays in the queue until its callback has run, and
the duplicate-download guard found it there: the same URL pushed from
the failed transfer's callback was refused as already in flight. The
achievement badge downloader retries that way after a network error,
so every retry was dropped, the badge's callback data leaked and the
badge queue stalled.

A finished transfer is no longer in flight, and the guard passes it
over now, in task_http.c and task_http_emscripten.c alike.

http_transport_failure_test gains a lane that fails a GET and pushes
the same URL from its callback: the retry must be taken and reach its
own callback. It fails on the old code.
2026-11-02 16:22:17 +01:00
LibretroAdmin 2b2c1703f2 task_wifi: Connect, Disconnect and Enable during a scan are held
One Wi-Fi task at a time, and any other pushed while one ran was
turned away without a word. The networks list pushes a scan every 30
seconds while it is shown, and a scan can take seconds: Connect,
Disconnect and the Wi-Fi Enabled toggle chosen during it did nothing,
the toggle leaving the setting and the driver apart.

They are held now, in order, and pushed as the task ahead of them
finishes, from a callback every Wi-Fi task is given that runs the
caller's own first. Only a scan behind a scan already running or
held is refused: it would find the same. Past a bound of four a task
is refused as before. The task in flight is tracked by its handler
rather than found in the queue, where a finished task is still found
while its callback runs. Frame thread only, where the menu pushes
these and their callbacks run: no lock.

wifi_tasks_test runs task_wifi.c on the real threaded queue with a
driver whose scan takes 300 ms: everything pushed during it is taken
and runs after it in order, one on the driver at a time, each with
its callback, and a second scan is refused. It fails on the old code.
CI runs it under ASan + UBSan and TSan.
2026-11-02 16:22:17 +01:00
LibretroAdmin 2ee96b13d7 task_screenshot: a screenshot asked for during another is held
One screenshot that writes a file runs at a time, and one asked for
while another encoded was turned away without a word: the Screenshot
hotkey did nothing while a save's thumbnail or an achievement's
capture encoded, a second press straight after the first was lost,
and a save's thumbnail was skipped behind a screenshot.

It is held now, in order, and pushed as the screenshot ahead of it
finishes: its callback hands the claim on. Each held screenshot keeps
its own copy of the frame, taken when it was asked for, so it shows
what was on screen then. Past a bound of four it is refused as
before. Frame thread only, where take_screenshot() and the callback
run: no lock, and nothing per frame.

A screenshots-held lane in the content_load harness takes a second
screenshot while the first encodes and one behind a save's thumbnail,
and checks every file is written. It fails on the old code.
2026-11-02 16:22:17 +01:00
LibretroAdmin 557107b598 task_save: a save or load pressed during another is held, not dropped
State tasks are TASK_TYPE_BLOCKING, so the queue takes one at a time
and refused the rest, which were then thrown away without a word.
Load State pressed while a save was still writing did nothing, nor
Save State during a load, an undo or an automatic save: the hotkey
had to be pressed again once the other was through.

A state task the queue turns away is now held, in order, and pushed
as the one ahead of it finishes; past a bound of four it is refused
as before. The save a backup load was taken for goes ahead of
anything held, so a Load State pressed straight after Save State
loads what that save wrote, not the file it replaced. The pending
flags become counts, so a content close, a quit and a movie recording
wait for held requests too. Anything still held when the savestate
backups are reset is discarded and its caller told.

All of it is on the frame thread, where every state push and every
state callback runs: no lock, and nothing per frame.

save_state_io_test's exclusion test now expects the hold and its
bound, and a new test checks that a load pressed during a save is
taken, counted as pending, and applies what the save wrote. The
state-load-latency lane gains Load State in the same frame as Save
State. Both fail on the old code.
2026-11-02 16:22:17 +01:00
LibretroAdmin 400dad6bcb tasks: a screenshot or a scan no longer turns Load State away
Screenshots, Bluetooth scans and Wi-Fi tasks were TASK_TYPE_BLOCKING,
and the queue refuses any blocking task while another is running. A
state save or load is one, so Load State pressed while any of them ran
was dropped without a word: nothing happened until it was pressed
again after the other task was through. A save's thumbnail, a
full-size capture of a 4K output or a Bluetooth scan (up to ten
seconds) held it off, which looked like states taking a very long
time to load, at random and with every core.

Each keeps one at a time among its own kind instead and no longer
takes the blocking slot, so only another state task holds a save or
load back:
- a screenshot that writes a file takes a lock-free claim at the push
  and gives it back as it finishes;
- a Bluetooth scan is refused while another is live, and its window's
  end moves into the scan from a file static;
- a Wi-Fi task is refused while another Wi-Fi task is live.

The content_load harness gains a state-load-latency lane: with
threaded tasks and compressed states, Load State fired as the hotkey
does must reach the core within 30 frames after a save, beside the
save's thumbnail, beside a screenshot and after a video reinit. It
fails on the old code. The screenshot lanes find their screenshot by
its handler, not by its type. bluetooth_scan_test checks that a
blocking task is taken during a scan and a second scan is not.
2026-11-02 15:42:21 +01:00
LibretroAdmin 901ebff805 Temporal Upscaler Quality: label Native as 'Native (DLAA, anti-aliasing only)' 2026-11-02 14:21:14 +01:00
LibretroAdmin e78097a6ca Wii HID: the unplug lane waits for the whole reap
The reap closes the device after deinitialising the pad, so the lane
waits for the close as well before checking it, rather than reading the
close count while the worker is between the two.
2026-11-02 11:25:09 +01:00
libretroadmin 11e8931021 raac: savestates
raac_state_size/save_state/load_state: what the frames decoded so far
leave the next ones - each channel's stream state and overlap, the
coupling channels, the SBR elements, the QMF histories at the doubled
rate and the PNS generator - for a decoder opened with the same
configuration, which then decodes on exactly as the saved one. Any
other size or configuration is refused.

raac_state_test saves before every unit of mono, stereo, 5.1 and
22.05 kHz (QMF) streams and loads into a new decoder.
2026-11-02 01:57:54 +01:00
github-actions 88d482c4c2 Fetch translations from Crowdin 2026-11-02 00:16:39 +00:00
libretroadmin 513f559a36 rh264: savestates
rh264_video_state_size/save_state/load_state: the decoder between
access units - parameter sets, picture order, reference marking, the
DPB and output queue with their samples and motion. A loaded decoder
decodes on exactly as the saved one, and saves the same bytes again.
Pointers are not stored; a state from another layout is refused, and
a refused load leaves the decoder as after a seek.

A threaded decoder's last picture, open until the next one opens, is
closed by a save and shown when that opening would have shown it.

rh264_state_test splits seven clips at every access unit on one
context, four, and a pool; it runs in the samples and TSan lanes.
2026-11-02 00:42:47 +01:00
libretroadmin 8dc1f6e982 ratrac3/ratrac3p: save and load the signal history
A frame's output depends on what the frames before it left behind, so
a decoder recreated after a savestate load, run-ahead or rewind gets
the next frame wrong. state_size/save_state/load_state copy that
history out as bytes and back into a decoder made with the same
parameters.

The tests decode each stream once straight through and once loading
the state into a new decoder halfway, and the two must match exactly.
2026-11-01 22:52:28 +01:00
LibretroAdmin b28b8a4b9f audio: menu silence behind a callback core writes only what fits
A core with an audio callback runs behind the threaded wrapper, which
keeps the device blocking whatever the menu asks. At Menu Frame Rate
'Display Rate' the menu feeds silence by the clock, so it now writes
only what the device takes without waiting, keeping half its buffer,
and does not make up time the device could not take. The recording
still receives every frame. The menu runs at the display's rate behind
such cores again.

samples/audio/menu_callback_feed holds the feed against a device that
counts every write that would have blocked.

Fixes #19971
2026-11-01 22:33:01 +01:00
LibretroAdmin 050acdd596 audio: drop a kept device before the next one is built
A kept device that does not match the next core's settings - a core
override of the audio latency, say - is closed before the pipeline is
rebuilt and before the next device opens, so it is released when the
next one may want the same device.

The threaded wrapper freed while parked now leaves from the park: it
no longer starts the device and runs one consumer pass on whatever
pipeline is current, nor reports a departed consumer or wakes it.
alive is published before the release from the park so the thread
sees it on the way out. Lane: pipeline_wakeups KEPT_DROP.
2026-11-01 21:14:06 +01:00
hizzlekizzle 3bf1482c16 Merge pull request #19935 from OpenSauce04/ios-simulator-core-fix
apple: Fix mishandling of iOS/tvOS simulator core dylibs
2026-11-01 08:41:58 -06:00
github-actions 98860742b6 Fetch translations from Crowdin 2026-11-01 00:18:41 +00:00
LibretroAdmin c74c00f5f8 asio: teardown parks on the last callback out
A callback may still be inside after ASIOStop, so teardown and the
reclaim of a parked instance waited for the in-callback count to reach
zero by yielding and then sleeping a millisecond at a time. The last
callback out now notifies an eventcount and the wait parks on it; the
200 ms deadline remains only for a driver whose callback never returns.

The eventcount is built for Win32 ASIO builds without threads too, as
it already is for DirectSound, XAudio and waveOut: the callback runs on
the driver's thread either way, so the write's park no longer falls
back to Sleep(1).

samples/audio/asio_teardown runs the driver's count and wait, pulled
out of asio.c, under TSan, ASan and wine; a build whose last callback
does not notify fails.
2026-11-01 00:11:58 +01:00
LibretroAdmin 9a807b7610 pk: no thread waits for the curve constants
The P-256 and P-384 constants are computed on first use. The thread
that claims a curve computes them into it; one that finds another
thread computing them makes its own copy on its stack for that call
instead of yielding until the other is done.

test/crypto/ec_first_use_test makes eight first uses at once under TSan
and ASan, and holds one thread in the middle of the set-up while
another's first use must finish with the same key; built to wait for
the held set-up, that must fail.
2026-10-31 23:40:08 +01:00
LibretroAdmin 287f215a75 core_info, decoder device slot: retire through retro_retire.h
The core info list read by the content scanner's task threads, and the
renderer's device handed to Apple decoder setup, each carried their own
copy of the last-reader-frees scheme. Both now use retro_retire.h,
whose handshake is sequentially consistent operations only; core_info's
copy relied on fences, which ThreadSanitizer cannot follow.
retro_retire_init() brings up one that is not zero-filled.
2026-10-31 22:03:09 +01:00
LibretroAdmin b2846c2ba3 Mutter DisplayConfig: the worker never waits on its callers
The worker publishes each display snapshot by swapping a pointer, and
waited for every caller to leave the one it replaced before freeing it.
It now hands the old snapshot to retro_retire.h: the last caller out
frees it, and the worker carries straight on with Mutter.

The threads case of samples/gfx/mutter_displayconfig reads the list
from four threads while the mode is switched; freeing a snapshot with a
caller still inside it is a TSan data race there.
2026-10-31 21:26:06 +01:00
libretroadmin 311fed569e menu: view Images history entries in place
OK on an entry of the Images (image history) list opens the image in
the menu viewer at once, in all four menu drivers, instead of offering
Run and loading it as content - a load that the menu then turned into
a file browser at the image's folder, so Back went to that browser.
Back now closes the viewer and leaves the Images list as it was, the
same entry selected; on Ozone's Images tab that also needed the
depth-one Back to stop moving the selection while the viewer is open.

An entry whose file is gone, or with the built-in image viewer off,
keeps its Run action. The menu-media workflow runs a new history-image
lane through each driver's real input path.
2026-10-31 19:50:57 +01:00
LibretroAdmin 041f4d8abb ci: PS2 runtime installs through apt-install.sh
The run job checks out RetroArch beside Play!, so the shared apt
scripts are there, and lists its build packages in APT_PKGS for
apt-install.sh with the job's apt cache.
2026-10-31 19:46:07 +01:00
LibretroAdmin fab8d5fe5d ci: PS2 whole-file I/O under Play!'s AutoTest
samples/frontend/ps2_whole_file reads, parses and writes whole files
through the shipping file_stream.c, vfs_implementation.c and
config_file.c - the path core info files and retroarch.cfg load
through. Built with PS2=1 it runs on the console's own runtime
(libcglue and fileXio over host:); a plain 'make check' runs the same
checks on Linux and Windows.

PS2-runtime.yml builds the ELF in the PS2 release image and runs it
under AutoTest from a pinned Play! commit, failing on any FAIL line or
a missing PASS verdict.
2026-10-31 18:38:54 +01:00
libretroadmin a8e2dbedda screenshot: add to the image history without the imageviewer core
The screenshot task pushed to the image history only under
HAVE_IMAGEVIEWER, which normal builds no longer set now that images
open in the menu's own viewer. The image history and the Images tab
it feeds live under HAVE_IMAGEVIEWER || HAVE_MENU, so screenshots
were saved but never listed there. The push now takes that guard.

The content_load harness gains a lane that takes a screenshot through
the shipping task and checks it heads the image history.
2026-10-31 18:28:43 +01:00
LibretroAdmin b222df8ddf vfs: only a negative close() is a failure
ps2sdk's close() passes iomanX's result through, which is the closed
descriptor number on success, so the descriptor path treats only a
negative return as a failed close. Whole-file reads on PS2 - core info
files and retroarch.cfg - depend on it.

vfs_close_result_test builds the VFS for PS2 with a close() that has
those semantics and runs in the Linux, Wine, glibc/macOS and Windows
sample lanes.

Fixes #19952
2026-10-31 18:27:47 +01:00
Codex 45e3482164 test: populate screenshot benchmark frames before timing
Touch and fill the source frame before starting the clock so the benchmark reads resident pixel data rather than untouched calloc zero pages. Keep snapshot allocation and rotation inside the timed callback.
2026-10-31 17:42:29 +01:00
Codex ef69f32bd5 screenshot: tile quarter-turn copies for cache locality
Rotate 90/270-degree snapshots in 32-pixel tiles so source columns and destination rows stay in cache. Select rotation outside the pixel loop and use a row-wise reverse copy for 180 degrees, retaining the one-buffer snapshot and packed output.

Exercise tile edges and non-square frames in the snapshot regression.
2026-10-31 17:36:46 +01:00
Codex 659f1174d1 screenshot: rotate cached frames directly into owned snapshots
Use the pixel format's byte width and the source row pitch instead of inferring pixel size from pitch/width. Build the packed, rotated snapshot while the cached-frame borrow is valid, avoiding a second full-frame allocation and the copy back into the first. Read only active pixels, including on the last padded row.

Cover rotations, padded and narrow frames, source lifetime, allocation failures and deferred screenshot ownership on Linux and Windows.
2026-10-31 17:36:46 +01:00
libretroadminandClaude Opus 5.5 b6b0e8b237 video: SET_ROTATION reports success before the driver instance exists
Cores request rotation from retro_load_game, which runs before
drivers_init(), so video_driver_set_rotation() sees the vtable but no
instance yet. The rotation is latched in the system info and applied
by video_driver_init_internal(), so the request does succeed; report
it as such and only skip the driver call. Returning false sent cores
such as mame2003-plus to their internal rotation fallback (#19957).

A driver without set_rotation, or no driver at all, still reports
failure. Covered by set_rotation_deferred_test in the Linux gfx
samples lane, which runs the function extracted from video_driver.c.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LBQPGgGpgC8aYP7saK5N72
2026-10-31 17:35:28 +01:00
LibretroAdmin 1ac696ff04 TLS: the session cache without a lock
The session cache is shared by every thread making TLS connections, and
every lookup and store took a flag spun on with sthread_yield. Where the
pointer atomics are, each slot now points at an entry that is never
changed once published: a handshake that learns a ticket, a master
secret or a TLS 1.3 PSK builds the host's entry anew, swaps it in with a
compare-and-swap, and the entry it replaced is freed once no reader is
in. Builds without the pointer atomics keep the flag.

queues/retro_retire.h holds that last-reader-frees scheme for both this
and the x509 trust store, which used its own copy.

test/net/tls_cache_threads_test stores and reads entries for more hosts
than there are slots from several threads, and holds a read on its
entry while a store for another host must finish; built on the flag,
that must fail.
2026-10-31 17:15:04 +01:00
LibretroAdmin ec085ffc31 x509: the trust store without a lock
TLS handshakes on several threads walk the process-wide trust store
while a reload may replace it, and every walk and reload took a flag
spun on with sthread_yield. Where the pointer atomics are, a walk now
counts itself in and reads the published store, and a reload swaps its
store in with a compare-and-swap; the store it replaced waits on a list
and is freed by whoever is last out, so no walk or reload waits on
another. Builds without the pointer atomics keep the flag.

x509_store_threads_test holds a walk just as it takes the store and
requires a load of another store to finish meanwhile; built on the
flag, where the held walk keeps it, that must fail.
2026-10-31 15:45:04 +01:00
Codex cbe5e51259 translation: narrow native PNG replies in place for RGB565
Reuse decoded PNG storage for a matching RGB565 software frame and narrow its pixels in place. This removes the output allocation and scaler context while preserving the decoder buffer's existing lifetime and the conversion path for resized replies.

Verify pointer identity, colors, zero additional image-task allocations, and allocation failures in both reuse and resized RGB565 paths.
2026-10-31 14:11:33 +01:00
Codex 17d0d04bc9 pixconv: support forward in-place RGB565 narrowing
Define the positive, pixel-aligned stride contract for ARGB8888-to-RGB565 conversion in the same buffer. Use byte accesses in the scalar path so overlapping 32-bit input and 16-bit output do not violate C aliasing rules; retain the existing alias-safe SIMD loads and stores.

Check all RGB565 colors, write bounds, short widths, vector tails and packed or padded output rows.
2026-10-31 14:11:33 +01:00
LibretroAdmin 89ffd86ade image: use memmove when duplicating upscale rows
The overlap checker flags the row copies because they share a base buffer. Use memmove to keep row duplication compatible with the check without an allowlist entry.
2026-10-31 14:08:42 +01:00
libretroadminandClaude Opus 5.5 689518cf6b Top a resumed blocking writer up to rate control's setpoint
A resume fills the drained device with silence before the core's first
audio. With a blocking writer it now fills only to rate control's
setpoint, half the buffer, instead of to full, where the writes after it
waited for the surplus to play out: a hitch of a frame or more on every
return from the menu, a state load or any other jump, which a VRR display
shows. A non-blocking writer keeps the full top-up as its cushion.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LBQPGeAnyXTjoifK18NSfm
2026-10-31 14:06:30 +01:00
Codex c04020a17c translation: reuse native PNG reply frames
Move decoded PNG pixels into the output slot when their geometry and native format already match the software frame. This avoids a full-frame output allocation and copy, as well as scaler setup, while retaining opaque XRGB alpha and the existing cleanup lifetime.

Check pointer identity, zero additional task allocations, guarded hardware/HDR/invalid frames, and allocation failures in both reuse and conversion paths.
2026-10-31 13:57:32 +01:00
Codex 9b10d1e99a translation: draw replies without intermediate image copies
Borrow validated BMP pixels from the response for the synchronous scaler call. Feed decoded PNG ARGB pixels directly to the scaler instead of allocating and flipping a BGR copy, preserving opaque software-frame output and keeping decoder cleanup separate from borrowed data.

Check colored PNG orientation and alpha, RGB565 output, unaligned BMP payloads, allocation failures and borrowed-buffer lifetime. Run the reply harness on Linux and Windows with and without PNG support.
2026-10-31 13:52:47 +01:00
LibretroAdmin f8b15a976d joypad_connection: a retiring slot parks for its last reader
Retiring a pad's slot closes its gate and waits for the readers still
inside; it waited by yielding in a loop. It now parks on an eventcount
that the last reader out of a closed slot notifies, so the thread
retiring a pad (a HID driver's worker, or the main thread at teardown)
sleeps until the slot is empty. A live slot's readers never notify.

samples/input/connection_gate holds a reader inside while the slot is
retired and requires the retiring thread to park; a build without the
eventcount must fail.
2026-10-31 13:50:43 +01:00
Codex a4206fbedd scaler: sample BGR24 downscales directly into the output
Fuse BGR24 point sampling with ARGB8888 or RGB565 output conversion for reductions of at least four source pixels per output pixel. This avoids full-frame input and output conversion buffers while preserving the existing near-unity SIMD path.

Extend pixel, signed-stride, workspace and benchmark coverage to both BGR24 input paths.
2026-10-31 13:41:45 +01:00
Codex eeeac63263 scaler: implement RGB565 output conversion
Bind the missing ARGB8888-to-RGB565 converter for direct conversion and resized output. This lets translated BGR24 images return to RGB565 cores when their dimensions differ from the frame. Use truncating channel reduction with SSE2, NEON and scalar implementations.

Check RGB565 roundtrips, SIMD tails, signed strides and padding, plus point/bilinear/sinc scaling from ARGB8888, BGR24 and RGB565.
2026-10-31 13:41:45 +01:00
Codex 3ceae4fe9d scaler: fuse point downsampling with pixel conversion
Convert selected RGB565 samples directly to ARGB8888 or BGR24, and ARGB8888 samples directly to BGR24, without full-frame conversion buffers. Keep filter validation and use the existing SIMD conversion path for enlargements and reductions below four input pixels per output pixel.

Cover sampling, signed strides, allocation behavior and all RGB565 values against the existing converters.
2026-10-31 13:35:32 +01:00
LibretroAdmin 757baea0da retro_eventcount: park on os/gekko's futex
The GameCube and Wii on os/gekko (GEKKO_NATIVE) have a futex, the
address wait the Linux, Switch and 3DS backends park on, but took the
condition variable and its lock on every notify. They now park on the
epoch word with gk_futex_wait and wake with gk_futex_wake; a timed wait
is turned into time base ticks and clamped rather than wrapped. The
libogc builds keep the condition variable.

retro_eventcount_test builds the backend on the host with GEKKO=1,
against a slice of os/gekko and its futex stood in for over Linux's at
the Wii's time base rate; check also requires a build that loses every
wake to fail.
2026-10-31 13:30:29 +01:00
Codex ae8e359e3f image: prevent channel-sum overflow in large box averages
A 4096-square packed 10-bit white image reduced to one pixel produced
cff3fcff rather than ffffffff: channel sums overflowed unsigned int.
The 8-bit path has the same problem at an 8192-square box.

Use wide accumulators only above the largest safe power-of-two factors:
2048 for 10-bit and 4096 for 8-bit. Retain the existing 32-bit loop for
normal thumbnail reductions, including its truncating division.

Check both safe boundaries and large-factor colored output through the
image scaling API, with ownership cleanup and allocation counts.
2026-10-31 13:27:41 +01:00
Codex 36ed2fccf4 image: duplicate power-of-two thumbnail rows directly
Expand each source row once and copy it vertically for power-of-two
thumbnail enlargement. These factors are exact in the existing 16.16
point mapping, so 8-bit and packed 10-bit pixels remain identical while
filter-table setup and repeated point sampling are avoided. Keep the
generic scaler for other factors.

Check dimension multiplication and the pixel limit before allocating,
including on 32-bit size_t targets. Exercise 2/4/8/16x output against the
existing sampling reference, 3/5x fallback, metadata and allocation retry.
2026-10-31 13:27:41 +01:00
Codex 7b98088caf image: retain exact box-decimation output without copying
When box decimation already produces the target dimensions, the scaler's
same-size sinc stage is just a copy. Transfer the decimated buffer into
the image instead of allocating and copying another destination.

Preserve pixel averaging, metadata and the sinc fallback for nonmatching
dimensions. Test 8/10-bit pixels, allocation-budget success, failure/retry
and ownership cleanup on Linux and Windows.
2026-10-31 13:27:41 +01:00
Codex 028638b0b2 scaler: omit the unused point-sampling frame workspace
Point sampling copies from the canonical input to the canonical output
without using the horizontal-pass frame. Keep its geometry validation,
but omit its allocation, clearing and metadata for point contexts.
Native ARGB8888 and packed 10-bit point scaling need no frame arena;
format conversions retain their input/output frames. Bilinear and sinc
keep their workspace and filter setup is unchanged.

Test native, converted and 10-bit pixels with padded/signed strides,
mode reuse, frame-allocation failures and oversized geometry. Run the
sample under Linux sanitizers and Windows/Wine.
2026-10-31 13:27:41 +01:00
Codex eedac7f2fb ci: register the list allocation sample with the shared runner
The shared sample job builds the all target and requires every executable
to be classified. Give file_list_oom both so it can run there as well as
in the dedicated Linux/Windows allocation checks.
2026-10-31 13:27:41 +01:00
LibretroAdmin 2855f460d8 smb: check the client socket instead of the server context list
Recent libsmb2 only adds accepted server contexts to its active list, so smb2_context_active() rejects connected clients. This makes the external backend fail seek/read/tell immediately after opening a file. Check the client's socket instead, retaining creation/destruction serialization for older libsmb2 releases.
2026-10-31 13:12:17 +01:00
LibretroAdmin 13b28f7568 Wii (libogc): the device thread waits without a lock
Nothing reports an SD card or USB drive coming or going, so the thread
looks once a second. It waited out that second on a condition variable
under a mutex it held the whole time; it now waits on an eventcount,
and stopping it is an atomic flag and a notify that wakes it at once.

samples/frontend/gx_devthread runs the thread against libogc and libfat
stand-ins: a drive put in is mounted, a card taken out unmounted, and a
stop returns at once; a stop that does not wake the thread must fail.
2026-10-31 12:54:53 +01:00
Codex 39b0c66c37 ci: exercise capture and allocation regressions on Linux and Windows
Run the new list and conversion tests under ASan/UBSan, then cross-build
and execute them under Wine. Exercise the production-source Vulkan, menu
and translation guards with both native and MinGW compilers.
2026-10-31 12:52:23 +01:00
Codex d513243a6e video: fuse thumbnail downscaling and propagate conversion failures
Sample and convert XRGB8888, RGB565 and BGR24 directly into the BGR24
destination when downscaling. Match the existing point scaler's fixed-point
sampling and RGB565 expansion without full-frame ARGB intermediates or
filter allocations. Keep the generic scaler for enlargement.

Return conversion setup failures to screenshot and translation callers.
Reject changed software snapshot dimensions before writing to a destination
allocated from the earlier snapshot. Translation readbacks already at the
requested size transfer buffer ownership instead of allocating and copying
a second image; resized readbacks use the fused helper.

The regression compares pixels and padding against the generic scaler for
three formats, six size pairs and four stride orientations. It also checks
failure recovery, small output, and translation ownership and bounds.

Failure propagation was motivated by PS5_RetroArch port change 0086.
The fused downscaler and translation ownership transfer are new work.
2026-10-31 12:52:23 +01:00
Codex 3ee4a4988d vulkan: offer properties2 instance queries on all platforms
Optionally enable VK_KHR_get_physical_device_properties2 wherever the
implementation advertises it, rather than only on desktop Windows.
Vulkan 1.0 cores may need these queries during context negotiation.
Move the existing entry so Windows does not request it twice.

Inspired by PS5_RetroArch port change 0089.
2026-10-31 12:52:23 +01:00
Codex 69e8babb01 vulkan: reject resized captures and release HDR uniforms on SDR
A cached-frame render may change viewport dimensions after a screenshot
caller allocated its destination. Reject that capture before copying,
rather than clipping into an output with a different row layout. Apply
the guard to SDR/BGRX and native HDR captures.

Both HDR uniform buffers are allocated regardless of runtime HDR support.
Release them outside the support conditional using the existing null-safe
destructor; retain conditional cleanup for HDR-only image resources.

Includes production-source guard and cleanup probes on Linux and Windows.
Inspired by PS5_RetroArch port changes 0090 and 0093; rejects dimension
changes instead of silently changing capture geometry.
2026-10-31 12:52:23 +01:00
Codex a1f6e62430 config: resolve the default input remapping directory
Resolve the literal "default" through the frontend's DEFAULT_DIR_REMAP,
instead of allowing it to become a relative directory named default.
Explicit paths are unchanged; a frontend with no remap default resolves
to the same empty value used during configuration initialization.

Inspired by PS5_RetroArch port change 0113.
2026-10-31 12:52:23 +01:00
Codex 895de2632a lists: reject incomplete entries before publishing menu rows
Check both string allocations before publishing a list entry. Construct
inserted entries before moving existing rows, reject out-of-range indices
and overflowing growth, and avoid reallocating an already reserved capacity.

Acquire menu callbacks before inserting a row and return them to the pool
if insertion fails. A rejected prepend must not reinitialize an existing row.

Includes deterministic allocation failures and immediate retry checks.
Inspired by PS5_RetroArch port change 0080; retains upstream's shared empty
strings and callback pools without adding a persistent failure latch.
2026-10-31 12:52:23 +01:00
Codex a8650d1dfb rthreads: reduce bespoke eventcount handoff overhead
Poll the epoch before publishing a Windows waiter or allocating its per-thread handles. Detach the wake list with a single CAS while respecting the unlink bit.

Preserve the alert, keyed-event and event tiers and high-resolution timeout handling. Add deterministic publication and unlink race checks to the Windows wait matrix. Separate benchmark control words from eventcount metadata and report ping-pong roundtrip time accurately.
2026-10-31 12:50:47 +01:00
LibretroAdmin 261128405c Wii HID: the worker's wake count is atomic
Readers, control messages and teardown all bump the worker's futex word
from their own threads; with a plain increment two of them could land
as one and the worker would sleep through a wake until its next scan.
The count is an atomic, and the futex waits on its word.

samples/input/gekko_hid now runs in CI under ASan and TSan, which
caught the plain increment.
2026-10-31 12:33:04 +01:00
Codex 4e26098355 Revert "rthreads: add native Windows eventcount address waits"
This reverts commit c1acf0363b.
2026-10-31 12:13:28 +01:00
Codex c1acf0363b rthreads: add native Windows eventcount address waits
Use WaitOnAddress without a library waiter-list lock on Windows 8 and newer. Retain legacy backends by default and add an opt-in strict mode that refuses lock-based backends. Native Windows timeouts round up to milliseconds.

Publish Linux spin calibration with CAS so another initializer can make progress while the first is suspended. Cover concurrent initialization, nested calibration, and strict backend selection.
2026-10-31 12:05:30 +01:00
LibretroAdmin c8c70837c7 D3D11: a core gives way to the frontend without spinning
libretro_d3d11.h version 2's lock on the immediate context lets a core's
outermost lock_context give way to a frontend waiting for it, since a
critical section is not fair. The core used to spin on SwitchToThread
until the frontend was in; it now parks on an event the frontend sets
once it is. The park and the frontend leaving the queue form a Dekker
pair on seq_cst atomics, so a parked core is always woken and the
frontend sets the event only when one is parked.

samples/gfx/d3d11_turns runs a core locking in a tight loop against a
frontend asking for the context, under Wine; a core that does not give
way must fail.
2026-10-31 11:56:36 +01:00
LibretroAdmin 65225e1d2c Wii HID: take pads away without sleeping
A pad that leaves is asked to stop and its reader cancelled; the worker
asks again on every pass, since a cancel only returns a transfer
already waiting, and reaps the pad once the reader says it is done. No
thread sleeps for it.

A driver freed while a reader still waits leaves the pad to that
reader, which frees it when its transfer returns. The driver itself is
freed by the last of it and the readers still running, so a late
reader no longer touches a freed driver.

samples/input/gekko_hid runs the driver against os/gekko stand-ins:
unplugging while the reader waits, with its cancel lost, and freeing
the driver under a waiting reader; a build that asks only once must
fail.
2026-10-31 11:39:31 +01:00
LibretroAdmin a4231fc04f Apple decoders: setup device hand-off without a lock
The renderer's device for VideoToolbox HEVC and VP9 decoder setup is
held in a decoder_device_slot: the lifecycle thread replaces it with
one exchange, and setup on any thread takes a retained copy. A replaced
device is released once no thread is between loading and retaining it,
by whichever thread is last out. The Metal snapshot's device,
generation and HDR flag travel in the same node, so a view is always
one consistent publication. The setup lock is gone.

samples/gfx/videotoolbox_vp9/snapshot_test.c replaces the device in
the middle of an acquire and races replacements against readers; a
build that releases on replacement is run as the negative lane.
2026-10-31 11:11:53 +01:00
LibretroAdmin b6743ae168 ffmpeg: recorder log lines and core subtitles without locks
The recorder's av_log callback builds each line in a slot of the
calling thread's own, claimed with a compare-and-swap and released once
the line is out, so lines from the encoder threads never splice and no
call waits on another.

The ffmpeg core hands each subtitle track's newest change to the thread
that renders through a one-slot mailbox swapped with a single exchange;
only that thread calls into libass, seeks included. A change supersedes
the one before it, so the held memory stays bounded when nothing
renders. Targets without pointer atomics keep a lock around the swap.

samples/cores/ffmpeg_ass_events checks the hand-off against a libass
stand-in, and samples/record/ffmpeg_pipeline gains a lane for whole log
lines from several threads.
2026-10-31 10:18:03 +01:00
LibretroAdmin 023bae90d7 tpool: the overflow without a lock, and workers that sleep
Jobs posted while the ring is full go on a lock-free stack, counted
before they are pushed. Whichever thread finds the ring empty and holds
the drain token takes the oldest for itself, moves as many more as fit
into the ring and wakes a worker for each; one turned away leaves word,
so the holder looks again before it lets go. Overflow nodes come from a
pool of spares on a free list tagged against reuse.

A worker that sees a job counted and cannot take one sleeps instead of
yielding in a loop, and passes on a wake that found it still marked
parked.

tpool_add_work joins the lock-acquire budget at zero.
2026-10-31 09:27:25 +01:00
libretroadminandClaude Opus 5.5 591237dc48 Clear what fast-forward queued when it ends
Leaving fast-forward drops what it left in the threaded pipeline's ring
behind a jump fade, and the device's writer drops core audio until the
device has played down to rate control's setpoint, so a blocking writer
is no longer held for as long as the queue takes to play out. That held
the core in device-period steps for seconds after every fast-forward,
which a VRR display shows as judder until the menu is toggled (#19940),
and cost every other configuration a hitch on exit. The five-frame
non-blocking window Sync to Exact Content Framerate used for this is
removed; it never drained the queue.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LBQPGeAnyXTjoifK18NSfm
2026-10-31 09:10:23 +01:00
github-actions 92b11d965b Fetch translations from Crowdin 2026-10-31 00:23:40 +00:00
sonninnos 6240401af4 Save task logging capitalization cleanup 2026-10-31 00:20:23 +02:00
sonninnos 33d69e1e6c On-Screen Display visibility option sublabel clarification 2026-10-31 00:16:44 +02:00
LibretroAdmin 989ba0b1df retroarch: carry the running audio across a content switch
The driver stage of a staged content load leaves the audio driver out
when its init would open the same device the same way for the next
core - same driver, device, output rate, latency, resampler and
threading, no DSP filter, time stretch or core audio callback, and the
core's float and multi-channel entries as they were. The device,
pipeline and mixer keep running through the rebuild of the rest; once
the video driver has settled the rates, the new core's rate is set in
place and the stream reset a core swap needs is done. When the rate
cannot be taken in place, the driver is opened again, the device kept
across as before. The driver lookup of the stage leaves a carried
driver's table alone.

The content_load harness's close keeps its tone playing with the
device never stopped, and a load with time stretch on sets the device
aside and takes it back.
2026-10-30 21:09:30 +01:00
LibretroAdmin e05c7d9240 audio: a system sound already loaded is kept on reload
The system sounds are loaded again after every driver stage. A slot
that already holds the sound from the same file now keeps it, and a
callback the load would have run - the menu music's play - runs on the
kept one; a slot the mixer lost loads again. A content load or a
driver reinit no longer cuts a menu sound that is playing over it, nor
restarts the menu music.

The content_load harness finds its tone where an install keeps its
sounds and loads it through the system sounds, so the reload at the
end of a confirmed Close Content runs against the sound playing over
it.
2026-10-30 21:09:22 +01:00
LibretroAdmin 65c4247d13 menu: hold the core title while a content load runs
The core title the menu shows stays the one captured when a staged
load starts - the core it is for, when its library is already open -
until the load is through, so it changes once instead of following
the libraries closed and opened between the stages.

The content_load harness reads the title every frame of a playlist
Run and checks it changes at most once, to the title the load ends
on.
2026-10-30 21:09:18 +01:00
LibretroAdmin b394e0bdfa runloop: a playlist Load State Auto pick loads the auto state
Auto, picked from a playlist entry's Load State list, loads the auto
state whatever Auto Load State is set to, through the same path as an
automatic load, deferred for a core that must run first.

The content_load harness's playlist lane picks Auto with Auto Load
State off.
2026-10-30 21:09:14 +01:00
LibretroAdmin 3894e03ef6 companion_thumbs: requests and results without a lock
Requests wait in two lanes of slots the UI thread fills; a worker takes
a job by swapping its slot to NULL, and the UI thread takes one back
the same way, so each goes to exactly one owner. A job is one record
from request to delivery, back through a lock-free list. animate()
hands the animation thread a whole request, holding the still it waits
for; the worker parks that still's session in an atomic slot and marks
the job landed. Prefetch is served oldest first, as the header says.

An eviction keeps an entry a decode in flight still points at.
2026-10-30 20:26:28 +01:00
LibretroAdmin 376110ae9b task_queue: GCD runs on the threaded runner without locks
GCD builds take the lock-free runner, with GCD's queues in place of its
workers. Each push of a task due now makes a call that takes tasks
from its ring until it finds none: plain tasks on a serial queue, so
they never overlap, parallel ones on a concurrent queue. A serial
keeper queue holds the tasks waiting for their time, with one timer
source, and every timed push and wake nudges it. Each init is a
generation its calls hold; deinit waits only for the calls past their
check, and leaves a detachable handler behind after the bound as the
threaded runner does. The locked GCD runner and task->sched are gone.

task_queue_deinit() waits for every thread still inside the wake gate
before the runner goes.

The GCD-capable samples also build against a libdispatch stand-in on
pthreads (GCD=standin), and task_queue_overlap_test checks which tasks
run at once on either runner, with pushes stopped halfway into the ring
through a test hook.
2026-10-30 19:08:45 +01:00
LibretroAdmin f6018d2b7b runloop: keep the audio device running through a staged close
Unloading the game inside a staged content switch ends the core's
audio with the pause tail instead of stopping the device: the device
keeps running until the driver stage sets it aside or closes it, so
the menu's feed and a menu sound playing over the close - the OK of a
confirmed Close Content - play on without being written into a
stopped device. The core stays gated until the drivers are rebuilt.

The content_load harness plays a tone through a confirmed close and
checks the device is never stopped while the menu feeds it and that
the tone it is handed is whole on both sides of the driver stage.
2026-10-30 19:07:12 +01:00
LibretroAdmin eb050c9d4f runloop: a playlist Load State pick travels with its load
Picking a slot from a playlist entry's Load State list records the
pick on the menu handle, and the content load it starts takes it in
event_init_content(), however many frames later that runs. The pick
loads that slot ahead of any auto state, and is dropped with a load
that fails before the content comes up.

The content_load harness runs an entry through the real Load State
list and checks which state the core unserialized, with an auto state
present and without one.
2026-10-30 19:07:09 +01:00
libretroadmin 715d38f577 video_thread_wrapper: replay asks gfx_widgets_visible() alone
gfx_widgets_visible() answers false for inactive widgets itself, so
the replay path assigns its answer directly. The replay test lane
drops the widget pointer and fps_show it set again after the loop.
2026-10-30 17:28:18 +01:00
LibretroAdmin ac2f474ca3 retro_eventcount: a woken semaphore park acquires its wake
A waker reads the parked block, sets WOKEN and then signals the
semaphore. A park woken through the semaphore now takes WOKEN with an
acquire before its frame goes, so the order the semaphore gives is one
ThreadSanitizer sees too; a Mach semaphore is not one it intercepts,
and on Darwin it reported the next park's write to the block as a race
with the waker's read.

The eventcount test's semaphore builds on Linux reach the C library's
semaphore past the interceptors, so they see no more order than Darwin
gives and the race is caught there.
2026-10-30 17:27:43 +01:00
JCorrêa 67b1c9ca8e fix(video): refresh widget visibility during inline replay (#19949)
Populate widgets_visible on the video thread during cached-frame replay,
which bypasses the normal worker loop. Keep UI composition and HDR
decisions consistent with the widgets being drawn without advancing
their animations again.

Extend the replay tests to cover visible widgets and stale visibility
after widgets are disabled.
2026-10-30 17:22:53 +01:00
LibretroAdmin e2d7b0b640 retro_atomic: 64-bit add/sub/or/and, halfword loads and stores
For the parallel-xbox core, which is moving QEMU's own atomics onto
retro_atomic.h: QEMU keeps 64-bit counters and flag words it adds to
and masks atomically, and a 16-bit halfword (the vCPU's exit request)
that other threads store to while generated code reads the 32-bit word
it is part of, so it cannot be widened.

retro_atomic_fetch_{add,sub,or,and}_64 are acq_rel and return the old
value, wherever RETRO_ATOMIC_HAS_64 is set; a backend with no 64-bit
read-modify-write of its own (32-bit MSVC, Apple OSAtomic) loops on
retro_atomic_cas_64. retro_atomic_u16_t has relaxed and acquire/release
loads and stores on every backend.
2026-10-30 16:30:36 +01:00
LibretroAdmin 27967e0b52 task_queue: titles and errors without property_lock
With the lock-free runner a task's title and error are also published
as atomic pointers beside the handler's own fields. Progress reports
and snapshots read the published copies inside a read epoch, and a
string replaced while a reader is inside goes through the epoch's
limbo instead of being freed at once, so a worker replacing its title
never waits on the frame that is formatting it. The getters return the
published copies; a title or error assigned to the field directly is
published when the task is pushed.

The locked runner keeps property_lock.
2026-10-30 16:17:00 +01:00
LibretroAdmin e09402e14f audio: the extra channels' slot table and count built together
The wide-frame renders count the extra positions while filling their
slot table, from the bits the frame carries, so the copy loops never
read a slot the table does not hold. This clears GCC's
-Wmaybe-uninitialized on the pipeline render; the inline transport
takes the same shape.
2026-10-30 15:27:00 +01:00
LibretroAdmin 755872d16b crc32: VPCLMULQDQ through assemblers that cannot encode it
GCC builds of the AVX-512 folds spell VPCLMULQDQ out in EVEX bytes with
a gas macro that reads the register numbers off the operands GCC
picked, so binutils older than 2.30 assemble them; the code is the same
instruction stream as the intrinsic form. The 128-bit tails stay out
of those functions so none of their PCLMULQDQ lands on XMM16-31.
<cpuid.h> is included once per unity build, since clang 12 and older
ship it without an include guard.

libretro-common/samples/encodings/crc32_gas checks every register
triple of the macro against the assembler and the SDM, that GCC's
assembly of the file at -O1 to -Os names no VPCLMULQDQ an old assembler
rejects, and that encoding_rzstd.c and encoding_crc32.c share a unity
build under an unguarded <cpuid.h>.
2026-10-30 14:30:59 +01:00
LibretroAdmin e6e980de33 android: Play Store builds deliver cores through Play again
play_feature_delivery_enabled() tested the isMetaQuestBuild method ID
rather than the flag read through it. The ID resolves on every build,
so feature delivery was off everywhere and Play Store builds offered
the whole buildbot core list through the core downloader.

The answer now comes from the build flags the frontend reads from Java
at init, and is not cached until it has read them. The
play_feature_delivery sample checks Play Store, Meta Quest and other
builds with both method IDs resolved, as on a device.
2026-10-30 14:09:02 +01:00
LibretroAdmin 5ba7f8c8cb task_queue: a threaded runner without locks
Where the atomics have compare-and-swap and pointer operations, the
threaded runner takes no lock and waits on no condition variable.

Every task is on a live list from its push until it is retired: any
thread links it at the head, only the checking thread unlinks it, and
find(), retrieve() and the lookups behind wake, cancel and reset walk
it from any thread inside a read epoch. A retired task is cleaned up
and freed once no finder can still hold it, at once when none is
inside, since finders read task->state.

Workers take ready tasks from two bounded MPMC rings: the plain one,
which only the one worker takes from, and the parallel one, which
every worker does; what overflows waits behind them in order. A task
with a 'when' to wait for goes to the timer keeper, whichever idle
worker holds the token, which sleeps until the earliest is due and
hands the token on before it runs anything. Main-thread tasks and
finished ones reach the checking thread through lock-free stacks.
Workers, task_queue_wait() and deinit's bounded wait for the workers
park on eventcounts. Each gather with work in flight and each pass's
end advance one counter that both read, so a handler still sees what
the frame loop wrote before a check, as the frame count a load waits
on, and has read it before the loop writes again.

The volatile and PS2 backends keep the locked runner, as does GCD.
task_queue.c now uses retro_eventcount.c, which the sample builds
compiling it with threads now link.

task_queue_parallel_test floods both rings past what they hold with
every worker held; task_queue_switch_test holds a waker in
retro_eventcount_notify() where the runner takes no lock.
2026-10-30 13:59:17 +01:00
LibretroAdmin 400d7ed4a3 audio: a format change no longer parks the audio thread
audio_driver_set_core_float() decides a source format change at once
and hands it to the threaded pipeline's consumer as a request word, to
install before its next pass; the producer writes nothing into the ring
until it has. A populated ring keeps its native format, as before, and
the producer converts its writes meanwhile. The stage a running or
suspended transport needs in the new format is made by the producer and
the old one handed back to it to free; one that cannot be made drops
the transport, keeping the new source native.

That was the last control to park the audio thread, so
audio_thread_apply_control() goes. Harnesses that parked the consumer
to look at its state stop and start the wrapper instead, and
thread_handshake checks audio_thread_parked() across its round trips.
pipeline_format hands its format change to a consumer pass and checks
nothing takes effect before it.
2026-10-30 09:42:01 +01:00
LibretroAdmin 8fcc656cb4 audio: transport changes no longer park the audio thread
Suspending, resuming, preparing and releasing the pitch-preserving
transport are decided on the producer's side, which keeps the transport
as it has decided it in pipe_transport_mode, and handed to the
consumer as a request word to install before its next pass. Nothing
waits and the device keeps running, so the speed changes that switch
the transport on and off no longer stop and start it.

The producer keeps writing meanwhile: the source in the ring is the
same whichever consumer takes it. A resume or a prepare still wants the
source drained, and puts its processing request into the metadata queue
behind what is already there rather than resetting the queue; the
consumer leaves it for a later pass while a short device write is
still to finish. A prepared stage is made by the producer, and the
replaced or released ones are handed back to it to free. With the
consumer parked the change is carried out in place, as before.

pipeline_wakeups' automatic transport lane checks the device is not
stopped across the suspension and the recovery.
2026-10-30 09:41:57 +01:00
LibretroAdmin 94d658724d audio: a rate change no longer parks the audio thread
audio_driver_set_input_rate() decides the change on the calling thread
and hands it to the threaded pipeline's consumer as a request word, as
the pause requests are, to install before its next pass. Until it has,
the producer writes nothing into the ring, so the ring, the slice and
the ratio are the consumer's alone and neither thread waits on the
other; the device is no longer stopped and started for it. With the
consumer parked, or gone, the caller installs the change itself.

A ring that has to grow is made by the caller and moved into by the
consumer with retro_spsc_grow_into(), which allocates nothing; the old
buffer and resamplers come back to the caller to free with the next
change or at teardown. The extra lanes of a multichannel core, which
used to refuse the change, are dropped with the front pair they share
tables with and remade at the new ratio by the next pass.

pipeline_rate_change hands each threaded change to a consumer pass and
checks nothing takes effect before it; pipeline_wakeups' kept-device
lane changes the rate under the real wrapper with the device running;
discrete_multichannel changes it with the extra lanes up; retro_spsc
moves a ring into a caller's buffer.
2026-10-30 09:41:53 +01:00
LibretroAdmin 0fc5e5356d audio: no state lock where the atomics have pointer operations
Without the threaded pipeline, the thread that submits the core's audio
holds the inline state: a submit takes inline_busy with a compare-and-
swap and records itself as the owner. The main thread's pause, resume,
format change and stop reset are published as request words, as the
threaded pipeline's already are, and carried out by that thread at its
next submit - before its gates, so a silenced core's thread writes its
pause tail - or at once by the main thread when it is the submitter, or
nothing has submitted yet. A callback core's thread settles them in its
loop. A submit that finds the main thread carrying a request out drops
its batch rather than wait. The core format the mixer follows is
published by the flush. audio_driver_sample_batch grows by the call
that settles requests on the submitting thread, and its hot-function
reference moves with it.

With that, every holder of the state lock is a pass that no other runs
beside, and the lock is only made where the atomics have no pointer
operations; elsewhere audio_driver_state_lock() counts the pass and
nothing more.

ff_speedup pauses a core that submits from its own thread and checks
the main thread writes no tail and that thread writes it at its next
submit.
2026-10-30 09:41:48 +01:00
3195d95a9b vulkan: end the HDR UI layer's render pass when a shader is loaded (#19946)
* vulkan: end the HDR UI layer's render pass when a shader is loaded

With HDR on and a shader preset loaded, the game pass ends the main
render pass and clears end_main_pass. When the menu, an overlay, a
message or a widget is drawn on top, the UI layer then begins
sdr_render_pass on the off-screen buffer, and with end_main_pass still
false nothing ends it. The composite that follows records its barriers
and begins the next render pass inside it, which Vulkan does not allow
(VUID-vkCmdBeginRenderPass-renderpass,
VUID-vkCmdPipelineBarrier-None-07889).

RADV draws these frames correctly anyway. On Android the touch overlay
is on by default, so every frame takes this path, and it is the likely
cause of the lost device in #19699 (HDR plus any shader on an Adreno
tablet).

* vulkan: test the HDR10 frame's render passes under the validation layer

No CI lane ran the Vulkan driver with an HDR swapchain: lavapipe on X11
has no HDR10 surface format. So nothing saw the UI layer's render pass
left open under the composite whenever a shader preset and any UI met,
which the previous commit fixes.

samples/gfx/vulkan_hdr_ui_pass runs the shipping frontend from the
VULKAN_DEBUG=1 build on lavapipe under an Xvfb of depth 30, which gives
the surface a real 10-bit format. A layer, fake_hdr_layer.c, reports
that format a second time paired with HDR10_ST2084 and passes an HDR10
swapchain on in sRGB with the same format and images. The driver then
selects HDR10 and takes its whole HDR path; what it records is
unchanged.

The cases: a two-pass preset with the menu open, with the statistics,
and with an OSD message; the menu case again on the video thread; and
the controls, the preset with no UI and the menu with no preset. Each
must create an HDR10 swapchain and log the HDR game pass and composite
pass it is meant to take, so a run that fell back to SDR, or did not
draw the UI, fails rather than passing vacuously. Any validation error
fails a case.

Without the previous commit, the four preset-plus-UI cases fail with
VUID-vkCmdPipelineBarrier-None-07889 and
VUID-vkCmdBeginRenderPass-renderpass, the two errors in the report; the
controls pass either way. It runs in the Vulkan validation workflow's
harness job, which already builds the tree VULKAN_DEBUG=1.

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

---------

Co-authored-by: libretroadmin <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-30 06:48:11 +01:00
JCorrêaandLibretroAdmin 03c3716444 fix(ps3) RSX: compose frontend layers correctly on duplicated frames (#19938)
* fix(ps3) RSX: compose frontend layers correctly on duplicated frames

Redraw the cached core image before frontend layers when a duplicated
frame needs presentation. This keeps widget animations visible without
covering overlays or statistics.

Use widget visibility instead of subsystem activity to avoid unnecessary
redraws. Present one cleanup frame when widgets disappear or are disabled,
and ignore empty OSD messages when deciding whether to redraw.

Add regression coverage for layer ordering, visibility transitions and
hardware, software and CPU fallback frames.

* RSX: take any removed frontend layer off a duped frame

One bit records whether the last presented image carries frontend
layers; a duped frame without any is presented once more over the
bare core image, so a closed menu, an expired message, statistics or
an overlay turned off no longer stay on screen until the next real
frame. Replaces the widgets-only bit; the ui_cleanup lane covers it.

* video: answer widget visibility once per frame in video_frame_info_t

widgets_visible is filled by the thread that draws the frame - in
video_driver_frame without the wrapper, and on the video thread after
gfx_widgets_worker_step with it - so drivers read one bool instead of
calling gfx_widgets_visible() at each decision. Every driver's views
UI layer, the Vulkan/D3D12 HDR UI routing and RSX's dupe presentation
read it; the widget-less variants of those calls collapse into one.

* gl1: present duped frames that carry frontend layers

A duped frame with the menu, an overlay, an OSD message, statistics or
visible widgets - or one that takes the last of them off - is drawn
again over the core image the texture still holds and presented, so
widget animations no longer freeze and their draws no longer pile up
in an unpresented backbuffer. A bare dupe stays unpresented.
gl1_draw_tex draws the texture as it stands when given no source.

* gl1/gl2/gl3: scRGB menu brightness only while widgets are on screen

The encode takes Menu HDR Brightness instead of paper white while any
UI is up; widgets now count when they are visible, as on Vulkan and
D3D12, rather than whenever the widget subsystem is enabled - which
kept SDR content at menu brightness all the time.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-30 06:40:57 +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
Davey Hughesandlibretroadmin 770391e8d5 scan: read an archive whole only when a database may hold its size (#19944)
* scan: read an archive's directory once for all its passes

The scan fetched an archive's first member's CRC, size and extension
through a helper that stops at the first entry and reports how many
the header counts, and its readers did the same.  One walk now reads
the whole directory, in the readers as in the scan: every member's
stored CRC, size and name, kept for each of the archive's passes.
Its transfer state is on the heap, out of the readers' frames.  The
walk replaces the helper in the archive passes; the helper stays for
its other users.

The walk decodes the first member for a CRC where the archive stores
none, a zstd stream or a RAR 5 member hashed with BLAKE2, so it is
looked up as before.  Later members are not decoded and keep a CRC of
0.  A .bz2 is decoded to be listed at all.

Folders are not members: an archive of a folder and one file has one
member, that file, and is looked up by its CRC first.

* zip: report a TorrentZip end comment

A zip whose end-of-central-directory comment is TORRENTZIPPED- and
eight hex digits was written by TorrentZip, as arcade sets are.  The
zip reader already reads that record to find the directory, so it now
says so in the transfer state, for a caller that wants the hint.  The
CRC in the comment is not checked: nothing depends on it.

* database_info, libretro-db: archive record sizes beside the crc index

The crc index build walks each database once, off the main thread.
In the same walk it now also collects the size of every record whose
rom_name ends in an archive extension (zip, 7z, apk, rar, zst), kept
in one sorted array keyed by size and type, so a caller can ask
whether a database may hold an archive of a given type and size.  A
record of that type with no size, or an index built without the
sizes, answers yes.  The sizes count against the index budget.

The stepped database scan reports one string field for this:
libretrodb_scan_begin() and its callback gain a name field.  A
record's rom_name can also be extracted on request.

* scan: look every archive member up from the directory

An archive whose first member missed was read whole and, when that
missed too, expanded: each member went through the databases again,
by the stored CRC the directory already held.  An archive of several
members was read whole before any lookup, even where no database
could hold it, so disc zips and console sets were read whole again.

One member: its stored CRC is looked up first, as before.  A hit in a
database claimed only for the member's type is held, and every
member's CRC is looked up next.  On a miss the archive is read whole
only when a database whose cores open its type may hold it by its
exact size; its own CRC is then looked up there, and where that is
not asked or misses, every member's.

Several members: the same size question comes first.  Each database
whose cores open the type is then asked for the first member's CRC,
and for the archive's own CRC only when the size let it be read
whole, so an arcade record still wins and no database claimed only
for a member's type is walked before it.  Where neither matches,
every member's stored CRC is looked up.

A database may hold the archive when it has a record whose rom_name
is of the archive's type and size, one of that type with no size, or
no sizes at all.  Every member is looked up with the gates and size
checks the expansion uses.  A hit names the archive itself only when
it is the one record hit, that record is named for this archive and
its database's cores open the type; other hits are written as the
expansion wrote them, archive#member.  The expansion stays for scans
that keep unmatched files, for archives whose directory cannot be
read, and where nothing hit and a member stores no CRC, which only
the expansion unpacks for one.

Readers read an archive whole ahead only after a whole match, twice
as many after each such match up to the cap; any other verdict takes
the rest back.  A TorrentZip set of several members is still read
whole by a reader with no grant while some database opens zip.  An
archive over the readers' cutoff is handed back before a grant is
spent on it.

This narrows "several members are read whole first" to "read whole
when a database may hold it by its size".  A record whose crc is an
archive's own CRC but whose rom_name is not of the archive's type, or
whose size is not the file's, is no longer matched; each of FBNeo's
596 sets carries its zip's size.  A zip of a folder and one ROM takes
the one-member order.

* database_scan_test: rar archives through every pass

A RAR 4 archive of stored entries, written by hand as the zips are,
takes the passes zip and 7z take: a first member's hit where a core
opens rar names the archive, a later member's elsewhere is held as
archive#member, and the archive is read whole only while a database
whose cores open rar holds a rar record of its size.

* database_scan_test: define the fixture byte writers once

* archive_file: clear the TorrentZip flag for every archive

The flag was cleared only by the zip reader, so a transfer reused for
a 7z, a rar or an archive with no backend kept what the last zip said.
It is now cleared before any backend opens the archive.

* scan: readers read TorrentZip sets whole while the scan asks for that

Readers read every TorrentZip set of several members whole while some
database opens zip, but where no database may hold a set by its size
the scan drops that CRC, so a folder of sets no database holds was
read whole for nothing.  Each TorrentZip set the scan rules on now
says whether its own CRC was asked, and the readers read the next ones
whole only while it was.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-10-30 04:08:09 +01:00
Davey Hughesandlibretroadmin 642575470e vfs: return an SMB read-ahead connection with its pool's window (#19943)
* vfs: return an SMB read-ahead connection with its pool's window

A read-ahead stream turns off the client read-ahead on its
prefetcher's pooled connection while the prefetcher runs. It did so
before opening the file and left it off when the open or the
prefetcher failed, so the slot went back to its pool with no window.
On close it set the window from the current settings, but a pool
keeps the settings it was built with and is used again when the
settings return to them, so after a read-ahead change a connection
could go back to its pool with the wrong window. The window now goes
off only after a successful open and is restored from the pool's key
whenever it was turned off.

* smb_retro: switch the SMB settings under vfs_threads_test's workers

A new phase has a thread switch the settings between two connection
keys while the workers open, read and list, so slots are taken from
and given back to two pools as a settings edit would. The two-shares
phase covers a second share; this covers the key changing under them.

* smb_retro: test that a slot goes back with its pool's window

A stream with a prefetcher open while read-ahead is turned off now
checks, once it closes, that every idle pooled connection still has
its pool's window. This fails without the read-ahead fix before it.
smb_pool_windows_ok() makes the check, with a getter for the window
the built-in client holds.

The switching phase is driven by the workers, each moving the key on
after a round, instead of a thread spinning on smb_init_cfg(). It
switches across five keys, more than there are pools, half of them
with read-ahead off, so idle pools are evicted beside busy ones, and
it checks the windows once the workers are done.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-10-30 03:41:06 +01:00
LibretroAdmin 17244c53a2 audio: the consumer writes the pause tail, the main thread only asks
With the threaded pipeline a pause or resume no longer takes the state
lock or writes to the device from the main thread. It publishes its
discard as before and a request word, and the consumer, the only thread
that writes to the device, carries the requests out before its next
pass, oldest first: the period-matched tail and its mute for a pause,
the mute reset and top-up for a resume. A consumer parked for data
returns to do it, and a stop settles what is left, writing no tail into
the stopped device. Without the threaded pipeline the tail is still
written where the call is made, under the lock.

pipeline_wakeups' pause boundary checks the pause writes nothing from
the main thread, and waits for the consumer's tail before counting stale
audio.
2026-10-30 02:07:57 +01:00
github-actions 91dbd317d4 Fetch translations from Crowdin 2026-10-30 00:29:21 +00:00
LibretroAdmin 82bd0ed020 rtime: Orbis converts with localtime_s
Orbis libc exports localtime_s(), in the C11 Annex K order, where other
libcs have localtime_r(), so threaded PS4 builds take that rather than
the shared static localtime() fills. It writes only the nine int fields
of a struct tm, so the copy it fills starts zeroed.
2026-10-30 01:03:37 +01:00
LibretroAdmin e84db153cc audio: the DSP filter and the record tap swap without the state lock
Both are published with pointer atomics, and a holder of the state lock
- a pass - counts itself in and out of pass_seq. The main thread swaps
the DSP filter in or out without waiting for a pass: what it takes out
is freed at once when no pass runs, and otherwise once pass_seq has
moved on, at the next swap or the next frame. Readers outside a pass
see what the filter allows through dsp_caps. The record tap is
installed with a store after its begin and comes out with a
compare-and-swap; removal ends it once no pass that could have seen it
remains, and both still answer "not yet" while a pass runs. A backend
without pointer atomics changes them under the lock.

mixer_lock builds with the DSP filter, swaps and frees it while a pass
holds the state lock, and checks nothing is freed before the pass ends.
2026-10-30 00:43:42 +01:00
LibretroAdmin 7cb0ec72f0 audio: the mixer's stream table is held without the state lock
The main and task threads hold mixer_streams[] with a compare-and-swap
owner word and park on an eventcount only behind one another; the mix
never takes it. A play's stop callback, on the mixing thread, only
reports the finish - its sound's tag into a per-slot word - and the
table's next holder, or the main thread once a frame, settles it:
retiring the sound, or starting the next stream of a sequential run.
That callback used to start the next stream itself, inside the mix and
under the state lock the mix holds, which deadlocks against itself on a
non-recursive mutex. A stop recognises its play by the voice's serial,
so a voice the mix has finished and handed on is left alone. The
volatile backend, whose compare-and-swap is not atomic, holds the table
with the state lock.

A WAV is loaded straight from the caller's bytes: its load copies the
samples it keeps, so the copy made before it is gone.

audio_mixer gains a per-sound tag for the callback to read. mixer_lock
finishes a sequential play from a mix holding the state lock, plays,
stops, adds and removes from two threads while a third finishes voices,
and checks a WAV is built from the caller's bytes.
2026-10-30 00:05:36 +01:00
Codex 770acdea34 time: avoid unavailable localtime_r on Orbis
The Orbis SDK declares localtime_r, but its libc import library does not
export it. Use the existing localtime fallback on Orbis so threaded PS4
builds link again.
2026-10-29 22:35:17 +01:00
LibretroAdmin d0fd2541c1 win32: NVDA reached from ARM64 builds too
nvda_controller_win32.c carries the ARM64 procedure format strings of
NVDA's own controller client, alongside the x86 and x64 ones, and the
stub descriptor its current (2024.4) build uses; the strings for all
three are that client's byte for byte.

samples/frontend/win32_nvda takes NVDA_DLL64 and NVDA_DLL32, NVDA's own
x64 and x86 clients from its controllerClient zip, to make the same
calls through them against the stand-in first.
2026-10-29 22:31:34 +01:00
LibretroAdmin 7b0159d5b8 cloud_sync: hand calls and results to the worker without a lock
A blocking driver's calls reach the worker on a many-producer stack and
its results come back on another; the worker parks on an eventcount
when the sync is still open and the queue is empty, and leaves once it
ends. A call queued as the worker leaves either starts a new worker or
is taken by the leaving one. cloud_sync_deinit() waits for the worker
on an eventcount, still bounded by its timeout.

Without pointer atomics the calls run directly, as without threads.

The async test queues a sync from the worker's leaving point.
2026-10-29 22:31:19 +01:00
LibretroAdmin 9f366c2e01 win32: NVDA spoken to over RPC, nvdaControllerClient64.dll gone
frontend/drivers/nvda_controller_win32.c talks to NVDA's controller
interface over local RPC itself, as NVDA's controller client does:
bound to ncalrpc:[NvdaCtlr.<session>.<desktop>], its four procedures
called through NdrClientCall2 with MIDL's format strings - the x64 ones
NVDA's client carries, byte for byte, and the same procedures for the
x86 stack - so 32-bit builds reach NVDA too. rpcrt4 and the session and
desktop calls are resolved at run time; where any is missing,
PowerShell speaks as it does without NVDA.

samples/frontend/win32_nvda runs it, x64 and x86, against a stand-in
NVDA in the test: an RPC server for the interface as widl compiles it.
Every procedure arrives in order with its string as sent, non-ASCII,
empty and long ones among them; with no NVDA listening or after deinit
nothing arrives and the calls fail.
2026-10-29 22:21:11 +01:00
LibretroAdmin e9a553a660 Drop the remaining off-path locks and spins
- rtime_localtime() uses localtime_r(), or the MS CRT's per-thread
  localtime(); no lock, no spin, nothing for rtime_init() to make.
- faulthandler: install and remove claim one CAS state word; one that
  finds it taken fails instead of spinning or taking a lock.
- core_info: the scanner's core_info_database_claim() reads a published
  list under a reader count. A list taken down goes on a retired stack,
  freed by whoever finds the count at zero: the main thread or the last
  reader out. The mutex stays for backends without pointer atomics.
- cheevos badge queue and mutter's no-pointer-atomics fallback: their
  locks guarded nothing, and go.

core_info_claim_test races claims against list rebuilds; the
faulthandler test races installs.
2026-10-29 22:05:35 +01:00
LibretroAdmin c30af2cd3e tests: tests-other/ into samples/
The widget and autosave harnesses become samples of their own -
samples/gfx/widget_snapshot_ownership, samples/gfx/widget_task_lifetime
and samples/runloop/autosave_snapshot - each built and run by make
check like the rest, the task lifetime test with the widget defines its
stubs build against. The autoconf suite, its .ratst scripts and test
input configs move to samples/input/test_input; the workflows that ran
them follow. The SPIRV-Cross MSL binding testbench, which nothing built
or ran, goes.
2026-10-29 21:39:02 +01:00
LibretroAdmin 88c0088a9b lakka/libnx: their headers to os/lakka and os/libnx
lakka.h and lakka-switch.h join the rest of Lakka's OS layer in
os/lakka; switch_performance_profiles.h, which only HAVE_LIBNX builds
read, starts os/libnx. Includes and the iOS Xcode projects follow.
2026-10-29 21:39:02 +01:00
Codex e63e6b5e8b tests: hold Wii U HID reads in flight at shutdown
Pause mock read completions and wait for both live pads to queue a read before deleting the HID client. Otherwise the deliberately broken IOS can have no reads to lose and spuriously pass the negative test.

Free discarded mock requests so the negative test detects missing adapter teardown rather than leaking its own request records.
2026-10-29 21:37:32 +01:00
LibretroAdmin 8f5877bf6b coremidi: device removals reach instances through one atomic count
CoreMIDI's notification of a removed object moves one atomic count
on, and touches no instance; each instance's read and write look for
its endpoint again only when the count has moved, on the thread that
calls them, so a read or write lists no devices while none has gone.
The instance registry and its lock go, and with them the cap of eight
instances. The receive callback's shutdown flag is an atomic.

samples/midi/coremidi_driver builds the driver against a stand-in for
CoreMIDI: reads and writes list no devices until one is removed, a
removed input drops the notes queued from it, every instance finds a
removal, and removals notified from another thread run beside reads.
2026-10-29 21:33:38 +01:00
LibretroAdmin ef5a1293db openxr: the XR thread parks while the session is down
OpenXR has no way to wait for an event. While the session is down, or
xrWaitFrame cannot wait, the XR thread parks on an eventcount, and the
drawing thread makes the non-blocking xrPollEvent for it once a frame:
the events reach it decoded through an SPSC queue, and the first one
hands polling back. One word says whose xrPollEvent it is, so the two
never poll at once, and a stop wakes a parked thread at once, or after
the drawing thread's poll in progress.

openxr_core_test parks the thread, checks it polls nothing, starts the
session from the drawing thread's poll, and stops it parked and beside
a poll; a stop that never wakes it fails the run after 60 s.
2026-10-29 21:26:18 +01:00
LibretroAdmin 778f937839 gxm/exynos: frames copied with memcpy, memory/neon gone
The Vita driver copies the core's frame into its texture a row at a
time, the frame's width of each row, or in one copy when the core's
rows are the texture's; textures are allocated at their row stride,
the width rounded to 8. Both drivers copy with the C library's memcpy,
NEON-tuned on their targets, and memory/neon/memcpy-neon.S goes.

samples/gfx/gxm_frame_upload runs the driver's frame upload against
the psp2 stubs with guarded GPU memory: core rows longer, as long and
shorter than the texture's, widths not of 8, rows copied once each,
nothing written past the texture or read past the frame.
2026-10-29 21:25:58 +01:00
LibretroAdmin 3863c2c1fa openxr: the track space is handed to the frame loop, the GL host drops its lock
A swap publishes the new track space as a record - space, origin,
number and carry range - with one pointer exchange and parks the old
one on a lock-free retired list; the frame loop's thread takes the
current record at each frame and destroys the retired spaces then, as
their only other user. A projection drawn before a recenter's remake
shows in the new space by the record's carry range, so nothing
rewrites it and the projection keeps one writer.

With that, every runtime call of the desktop GL host is on its XR
thread, so the host has no lock. Vulkan keeps its queue lock, which
the core's lock_queue shares.

openxr_core_test swaps from a core thread while the frame loop
submits: every frame shows the carried projection in a live space,
and each space is destroyed once.
2026-10-29 21:02:20 +01:00
LibretroAdmin f23cc50ca4 android: play_feature_delivery/ to os/android
The Play Feature Delivery glue is Android's alone - JNI calls into
RetroActivityCommon - and the last platform directory of its kind at
the tree's root. It joins the other OS layers under os/; includes, the
Makefile, griffin, the compile matrix and the two samples that build
it follow.
2026-10-29 21:01:28 +01:00
LibretroAdmin 4d702934db wiiu/hid: hand adapters between threads lock-free
The polling thread and the frontend shared one adapter list under an
OSFastMutex, which the frontend took on every poll, and the HID attach
callbacks queued events by swapping the list head out and back in. That
swap pair is not atomic as a whole: two callbacks at once (the HID
stack calls them from more than one thread) can each take the list and
put theirs back over the other's, losing an attach or a detach. A lost
detach leaves the pad registered for good.

Each thread now keeps its own list and hands adapters across through
the lock-free stacks of mpsc_stack.h:

  attach callbacks -> hid_events  -> polling thread
  polling thread   -> hid_new     -> frontend: adapters to set up
  frontend         -> hid_retired -> polling thread: to free

An adapter's state is a retro_atomic_int_t, and each value is moved on
by one thread only, so no two ever write it from the same state. The
read completion runs the packet handler before it says READY, since
READY is when the polling thread may start winding the adapter down;
it used to say READY first. The frontend deregisters and deinitialises
a pad as it retires the adapter, the polling thread frees the adapter,
and at shutdown the polling thread tears down what is left, as before.
No lock remains, and the frontend's poll no longer takes one.

samples/input/wiiu_hid_threads runs the driver against stand-ins for
Cafe OS on pthreads: two threads attach and detach devices while the
frontend polls and an "IOS" thread completes the reads, and every pad
must be registered, fed, deregistered and deinitialised in order, on
the right threads, with nothing left at shutdown. Its negative build
loses the reads in flight at shutdown and must fail. Against the old
driver (32-bit PowerPC under qemu) it fails: a few hundred pads of
detached devices are never torn down.
2026-10-29 21:01:28 +01:00
Codex cd7769d049 Move RTMP and FLV helpers into libretro-common
Expose reusable net_rtmp and rflv_mux interfaces without frontend dependencies, including bounded TCP socket service. Use the SDK MIT license for these newly authored modules.

Move the portable tests, register optional RTMP/FLV build gates and exercise standalone SDK and frontend builds. Recording-specific capture and encoder integration stays in record/.
2026-10-29 20:57:33 +01:00
LibretroAdmin f43f043dca audio_mixer: the mix's passes reach no eventcount, PS2 drops its voice locks
A voice's owner word marks a caller parked on it, so letting a voice
go notifies only when someone waits: the mix takes and releases each
voice with one compare-and-swap and one exchange, and never reaches
the eventcount - nor, where its bookkeeping is locked, its lock. With
that the PS2's interrupt-masked atomics take the owner word too; only
the volatile backend, whose compare-and-swap is not atomic, keeps a
lock per voice.

audio_mixer_voice_threads_test holds one voice from two callers while
a mix takes it between them, and fails rather than hangs when a
caller is never woken.
2026-10-29 20:38:50 +01:00
LibretroAdmin 2179fefa35 Wii U: read-modify-write atomics go through coreinit
The Wii U's Espresso needs a cache flush inside a lwarx/stwcx. pair for
the reservation to hold across its cores, which no compiler emits and
Cafe OS's own atomics carry. retro_atomic.h gains a Wii U backend,
chosen ahead of the C11, C++11 and GCC builtins there: compare-and-swap,
exchange and fetch-add/sub/or/and call coreinit's OS*Atomic between the
fences their ordering asks for, while loads, stores and fences stay
inline. Across the Wii U build that takes every lwarx/stwcx. out of
RetroArch's own code. The newlib lock hooks, the exception handler's
first-fault flag and the SwapAtomic helpers the HID driver uses go
through it too, in place of the builtins they called directly.

libretro-common/samples/atomic/retro_atomic_wiiu_backend checks the
backend against stand-in coreinit exports: one call per
read-modify-write, the right one, with the previous value returned,
none for loads, stores and fences, and no update lost under threads.
2026-10-29 20:33:34 +01:00
LibretroAdmin b0b0691ff0 Vita: the bubble launcher moves to pkg/vita, its tools built from source
The LiveArea bubble launcher lives under pkg/vita/launcher. The two
host tools its build.bat runs, vita-mksfoex and vita-pack-vpk, are C
sources there in place of prebuilt Windows executables, built by the
builder's Makefile with the host compiler. vita-mksfoex writes the
param.sfo vitasdk's 2016 tool writes, byte for byte, and refuses a
title too long for STITLE instead of writing a broken one;
vita-pack-vpk deflates with libretro-common, storing what deflate
cannot shrink, and fails on two members under one name.

samples/pkg/vita_launcher_tools checks both against the reference
param.sfo outputs and reads each VPK back with libretro-common's zip
reader.
2026-10-29 20:33:30 +01:00
LibretroAdmin 5b3ec8d48a Emscripten: its JavaScript runtime moves to os/emscripten
The platform, audio and camera libraries, the pre-js and the closure
externs the web build links in live under os/emscripten.
2026-10-29 20:33:27 +01:00
LibretroAdmin 43ac210fb8 UWP: its runtime moves to os/uwp
The UWP entry point and helpers live under os/uwp. The UWP projects add
os to their include paths, so <uwp/...> as libretro-common names it
keeps resolving; RetroArch's own sources include them by their path.
2026-10-29 20:33:24 +01:00
LibretroAdmin e867b83f42 Lakka: cpufreq and reboot2payload move to os/lakka
The Lakka-only CPU frequency control and the Switch payload reboot live
under os/lakka, out of misc/; what includes them names their real path.
2026-10-29 20:33:21 +01:00
LibretroAdmin d21f92d784 Dingux: its utilities move to os/dingux
The Dingux helpers live under os/dingux with the other platforms' system
code.
2026-10-29 20:33:18 +01:00
LibretroAdmin 4964dfa774 bootstrap/ moves under os/
The libogc 2 linker scripts go to os/gekko/libogc, the PSP kernel
functions PRX to os/psp1 and the Vita heap's sbrk to os/vita, beside
each platform's other system code.
2026-10-29 20:33:15 +01:00
Codex b2356920ec record: add native FLV and RTMP publish protocol
Add bounded FLV muxing and worker-owned RTMP framing, handshake, control and AMF publish negotiation. Retain borrowed output across short writes and cancellation without frame-path I/O or waits.

Exercise standard and legacy publish sessions against an independent receiver, including name-only onFCPublish callbacks. Socket transport and native encoder delivery remain separate integration work.
2026-10-29 20:19:51 +01:00
LibretroAdmin 60d9cd7bdd slang: detached compiles go through glslang too
glslang's process state is kept for the life of the process once
started: a shader task can still be compiling when its video driver
goes, or when the task queue leaves it running at exit, so it is never
finished under one. With that, a detached compile - the deferred
load's precompile tasks - compiles with glslang as the chain's thread
does, falling back from rslc to it the same way, and hands back the
cache key of the compiler that made the stages.

The detached entry no longer takes the HLSL/MSL flag it ignored.
2026-10-29 18:43:25 +01:00
Craig 95fef538ee uwp: fix double 2026 in name (#19942) 2026-10-29 18:46:41 +01:00
Codex 1190da6e8a gl2: retain GLES3 recording readback dimensions
Track the PBO extent on GLES3 so viewport reads do not recreate the ring and discard every pending frame. Exercise the production initializer for ring retention and resizing on Linux and Windows.
2026-10-29 18:37:24 +01:00
LibretroAdmin bc8ff41a38 Wii: app_booter moves to os/gekko/rvl
The loader stub the libogc builds embed to hand the console to the next
program lives under os/gekko/rvl with the rest of the Wii's own code;
its binary object's symbols are named after the new path.
2026-10-29 18:33:29 +01:00
Davey HughesandLibretroAdmin 9d38efa2d6 scan: let the parallel readers open smb:// files (#19939)
* vfs: close a private SMB connection when an open, stat or opendir fails

When every pool slot is held, smb_take() connects privately for the
caller. If the open, stat or opendir then fails, smb_heal() returns
NULL when the connection is alive (a missing path, the usual case) or
cannot be replaced, and the caller stored that NULL over its context
and gave back nothing, so the connection and its socket were never
freed.

Keep the caller's context when smb_heal() returns NULL, so smb_give()
closes it. A pooled slot is given back as before.

vfs_test holds both pool slots with open files and then stats, opens
and lists a missing path; LSan reported the three connections before.

* vfs: release an SMB stream's connection when close follows a failed mend

When a stream's connection cannot be mended (the server is gone and
the reconnect is refused), or its file cannot be opened again, the
mend clears the stream's handle. Close returned early on the missing
handle, so a private stream leaked its connection, a pooled stream
kept its slot busy for the pool's life (smb_shutdown then left the
pool unfreed), and the reopen record leaked either way.

Close now checks for the connection rather than the handle and closes
the handle only when there is one. After a failed mend a private
connection is freed without a goodbye, since the server may be gone.

smb_idle_test gains a mode in which the server is stopped for good
during the pause, and local_samba_test.sh runs it, so the sanitizer
lane's LSan reports these leaks.

* cloud sync: make and drop SMB contexts under the VFS's list lock

libsmb2 keeps every context on one process-wide list with no lock of
its own, so the SMB VFS makes and drops its contexts under a lock
(4e6885d5a3). The SMB cloud sync driver called smb2_init_context() and
smb2_destroy_context() directly. A sync that began or ended while the
VFS connected or disconnected on another thread, as during a scan,
could lose a list entry, after which a live connection reads as dead,
or break the list.

The VFS exports its two wrappers and cloud sync uses them. The
built-in client has no such list; there they call straight through.

cloud_sync_test runs cloud sync's connect and disconnect beside private
VFS connections; the libsmb2 TSan lane reported the race before.

* scan: readers take smb:// paths when an SMB client is built

The scan's parallel readers were kept off every "://" path. smb://
paths go through the SMB VFS, whose connection pool serves several
streams at once, and the commits before this one fix the connection
leaks and the libsmb2 list race that readers would otherwise hit. So
readers now take smb:// paths in builds with an SMB client, built-in
or libsmb2; other schemes stay with the scan.

database_scan_test adds smb://, nfs:// and ftp:// archives to a
threaded scan's list and counts the readers' reads of each. Its TSan
build gets a stub SMB backend, so CI runs both halves.

* vfs: keep an SMB pool per share, never freed under its users

Pools live in a static table, one per connection key. Each pool's state
and user count share one atomic word: a lookup or a held slot is a use,
only an idle pool is evicted, and a retired pool closes its connections
when its last user leaves. Readers on two shares each keep their pool's
connections, and no thread touches a freed pool.

A failed reconnect after a dead transport frees the connection without
the round trips of a logoff; a pooled slot reconnects on its next take.

vfs_threads_test runs threads alternating between two shares of one
directory with no share configured.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-29 18:17:24 +01:00
LibretroAdmin ef7e36ff8b 3DS: salamander links with gcc
The salamander launcher has no C++ left in it, and the C++ in libctru,
its allocators, needs nothing from libstdc++, so it links with gcc;
the C++ build rules it no longer uses, and the shader rules it never
did, are gone.
2026-10-29 18:11:46 +01:00
LibretroAdmin bae87174dc 3DS: the runtime moves to os/ctr, linear allocator in C
The tree's 3DS layer (the custom crt0 and link spec, system and memory
setup, svchax, the old GPU command layer and the 3dsx/CIA launchers)
lives under os/ctr, beside os/gekko and os/wiiu.

The linear heap allocator is C: the same first-fit pool, so buffers
land where they did, with live allocations found by address in an
open-addressing table instead of a per-allocation tree node, and list
nodes taken from cache-line-aligned slabs and reused. On the same
trace it runs about half the instructions and practically no
malloc/free calls. With the whole heap allocated, a further allocation
now fails instead of starting the heap over and handing out its first
address again, and the free-space queries no longer fault.

samples/frontend/ctr_linear checks placement, alignment, coalescing,
the full-heap case and pages handed back from the heap's end.
2026-10-29 18:10:30 +01:00
Codex 66977c726a record: correct AVFoundation AAC queries and append failures
Use the format-ID specifier required by AudioToolbox when selecting AAC sample rates. Validate requested audio settings and retry with the encoder default bitrate when the preset is rejected.

Report refused or failed audio/video appends instead of returning success. Cover the property ABI, buffer release and writer states with portable regressions, and add a native AudioToolbox contract check.
2026-10-29 17:57:36 +01:00
LibretroAdmin 99df72ef0a glslang: start the process once per video driver, lock-free
glslang's process state is started in video_driver_init_internal(),
before the video thread exists, and finished in
video_driver_free_internal(), after it is gone; a compile only reads
one state word. Compiles share glslang's built-in symbol tables
instead of rebuilding them each time, and no global mutex or guarded
static stands in the compile path. A caller with no video driver
around it, such as a tool, starts the process on its first compile.

Against glslang 15 a compile takes 1.1 ms instead of 73 ms, with
byte-identical SPIR-V.
2026-10-29 17:54:08 +01:00
LibretroAdmin 6b5e615dc8 webOS: packaging moves to pkg/webos
The webOS ipk packaging - the icon, its build script, the aarch64
repack script and the notes - lives under pkg/webos with the other
platforms' packaging. Makefile.webos stages and packages the ipk there,
the webOS workflow collects its artifacts from there, and the Homebrew
Channel manifest takes its icon from pkg/webos on master.
2026-10-29 17:26:23 +01:00
libretroadmin 71bd36ac63 CLeanups 2026-10-29 17:23:46 +01:00
libretroadmin 15568b8c78 memset unnecessary here - struct fixed 2026-10-29 17:20:31 +01:00
LibretroAdmin 3c0c49e3ce Wii U: elf2rpl rewritten in C89
elf2rpl, the host tool that turns the linked Wii U ELF into an RPL, is
now a single C89 file, os/wiiu/wut/elf2rpl/main.c, in place of the C++
sources, their own ELF header, CRC-32 and endian helpers, and the
Visual Studio project files.

Big-endian fields go through retro_endianness.h; the byte swap the old
be_val.h picked by PLATFORM_WINDOWS/PLATFORM_APPLE/PLATFORM_LINUX is
gone with it, along with those defines - it did not build on any other
host and swapped wrongly on a big-endian one. Section CRCs and import
signatures come from libretro-common's encoding_crc32 instead of the
bundled bytewise table.

The input is read once and its sections used where they lie; symbol
and import lookups by address are hash tables; the output is laid out
in one buffer and written with one call. On a 7 MB core ELF with 41k
relocations the RPL comes out byte for byte as before, and so does the
log at every -v level, in about a quarter of the time (15 ms against
52 ms; 47 ms against 104 ms at -v -v -v -v).

Malformed input now ends in an error instead of a crash: header,
section, symbol and relocation offsets are checked against the file,
a stub with no symbol behind it is reported, and an unknown option is
refused rather than taken for the input file.

The tool is built with the host C compiler (HOST_CC, gcc by default),
which an exported devkitPPC CC no longer reaches.

samples/frontend/wiiu_elf2rpl builds the C tool and adds checks that
every section's SHT_RPL_CRCS entry is the zlib CRC-32 of its contents,
and that truncated ELFs and an unknown option fail cleanly; it no
longer needs the leak and alignment sanitizer exemptions the C++ tool
did.
2026-10-29 17:13:46 +01:00
LibretroAdmin d164f9ab0b Wii U: elf2rpl drops relocations against undefined weak symbols
devkitPPC's newer libc and libstdc++ reach the OS through weak hooks
(__syscall_exit, the __gthread_* ones) that the RetroArch runtime does
not define. A -pie link leaves each reference with its final value
written - zero, or a branch to itself behind a check that the hook
exists - and a relocation against the still-undefined symbol, which
elf2rpl handed the loader against $UNDEF: a symbol it cannot resolve
and a 24-bit branch to address zero, out of reach from the code area.
Every RPX linked this way fails to load with error 160-2203 before any
of it runs. Such a value does not depend on where the module loads, so
elf2rpl drops the relocation. Makefile.wiiu rebuilds elf2rpl when its
source changes.

samples/frontend/wiiu_elf2rpl writes a linked ELF in that shape, runs
elf2rpl on it and checks the RPL carries no relocation against an
undefined symbol while those against resolved targets survive.
2026-10-29 16:14:13 +01:00
LibretroAdmin 7986022baf uwp: move into pkg/msvc as msvc2022-UWP and msvc2026-UWP
The UWP solution lives in pkg/msvc beside the desktop ones, split per
Visual Studio release: RetroArch-msvc2022-UWP (v143) and
RetroArch-msvc2026-UWP (v145). Both projects share pkg/msvc/msvc-uwp
(manifest, assets, ANGLE/MESA libraries, core placeholders) and the
CppWinRT package in pkg/msvc/packages, with intermediates and generated
files kept per project. The MSVC workflow builds 2022-UWP on the
windows-2022 runner and 2026-UWP on windows-2025.

Closes #19893
2026-10-29 14:14:04 +01:00
LibretroAdmin 7dddd2c860 libretro-common: faster rinflate
The decode tables are built in one pass: each symbol's packed entry
is made as its code is reversed (through a byte table, not a bit
loop) and splatted beside the fast entry, instead of two 2048-entry
passes after the build; and nothing is cleared beforehand, since a
complete code set writes every index it can reach (an incomplete one,
which zlib accepts for a distance table, still clears). The fast
loop's refill is branchless: eight bytes shifted in above the count
on every iteration, the whole ones counted, the rest left above the
count where the next refill puts the same bits again; the loop's
limits are read once rather than reloaded after every output store.
A stored block takes the whole bytes the bit buffer holds first and
then copies the rest straight from the input, instead of going byte
by byte for the whole block whenever the buffer was not empty on
arrival, which after a Huffman block it never is.

On one machine, best of many runs: a 4 MiB binary at 43% is decoded
at 375 MB/s (338 before; zlib-ng 440, zlib 1.3.1 190), a literal-
heavy table at 272 MB/s (226 before; zlib-ng 304), and stored blocks
at 1.7 GB/s (1.1 before; zlib-ng 1.7). Checked byte for byte against
zlib's output over 1500 random streams of every level, wrapper and
window with the input and output handed over in random slices, under
the address and undefined-behaviour sanitizers.
2026-10-29 05:38:08 +01:00
LibretroAdmin 1d887619b0 libretro-common: rinflate resumes inside a gzip header
The gzip header was read in three pieces with a suspend possible
between them, and a resume started over at the first: input that
ended inside the ten fixed bytes (a slice boundary there) failed the
stream with an ID check on the wrong bytes. The identification and
flags are read in one step and the six bytes after them counted down
in the next, so a resume carries on where the input ended.
2026-10-29 05:38:05 +01:00
LibretroAdmin 4ecfe79beb libretro-common: rdeflate honours the window size in window_bits
The compressor always matched up to 32 KiB back whatever window_bits
asked for. zlib's convention gives the window's size in the same
argument (|bits| for raw and zlib, bits - 16 for gzip), and callers
rely on it: qcow2 compressed clusters are written with -12 so that a
4 KiB window decodes them. The match distance is now capped at that
window, and the zlib header's CINFO says which window was used.
2026-10-29 04:09:15 +01:00
LibretroAdmin 55fd7063e1 Linux Game Mode: follow kernel uevents instead of a 5 s timer
Without UPower, the Game Mode helper re-read /sys/class/power_supply
every 5 seconds to notice a charger being plugged in or pulled. It now
listens on the kernel's uevent broadcast (NETLINK_KOBJECT_UEVENT, group
1, the one udev itself reads; unprivileged processes may receive it)
and re-reads sysfs only when a SUBSYSTEM=power_supply event arrives, or
when the socket reports dropped events (ENOBUFS). The helper now never
wakes unless something actually changed.

UPower is also asked only once per Game Mode session: when it is
absent, later checks go straight to sysfs instead of making a D-Bus
call that is bound to fail.

The 5 s re-check remains only where the uevent socket cannot be opened
at all (some containers).

samples/frontend/gamemode gains a "nowake" lane: run under strace
without UPower, it checks that a uevent socket is opened and bound and
that no poll() ever uses the 5 s timeout. Skipped without strace.
2026-10-29 02:10:30 +01:00
github-actions b60bc37931 Fetch translations from Crowdin 2026-10-29 00:18:39 +00:00
Davey HughesandLibretroAdmin 40a5f4e6ae scan: look archives up by their first member's CRC first (#19937)
* scan: look archives up by their first member's CRC first

A zip, 7z, apk, rar, zst or bz2 was hashed whole before its first
member's CRC was fetched.  Cartridge databases key on ROM CRCs, so a
one-ROM-per-zip set always matches by the member: the whole read, the
main cost of scanning zips on a slow share, was wasted.

The databases are now walked first for the member's CRC alone.  Where
it is stored (zip, 7z, RAR with CRC32) that costs one directory read;
a zst, bz2 or BLAKE2 RAR member is decoded for it, as before.  Only
when that misses is the archive read whole, or a reader's whole result
taken, and the databases walked again for its own CRC, which the
arcade databases key on.  The archive is expanded only after both
miss.  Each walk's size check uses its own size: the member's, then
the archive's.

By default the core gate looks in a database only when a core claims
the file's extension.  Stock cartridge cores do not list zip, so a
cartridge zip was read whole and matched only once expanded.  The
first walk now also takes the databases claimed for the first member's
extension.  A hit there is held while the walk finishes the databases
claimed for the archive's name: a match in one of those is written for
the archive; otherwise the archive is expanded without the whole
read.  With "Scan Inside Archives" off nothing expands, so the first
walk keeps to the archive's name.

The parallel readers likewise start an archive with its first member's
CRC, size and extension, and hand them to the scan.  They hash
archives whole only as far as the scan grants: a member-CRC miss
grants one whole read ahead, each further miss in a row twice the
last, up to 16 at a time, and a member-CRC match takes back what is
left.  A lone unknown cartridge then costs one wasted read, while an
arcade folder soon has the readers 16 ahead.  While the scan first
walks a database's index in a member pass mid streak, the readers may
keep 16 ahead, so the walk does not leave them idle.  A grant goes
first to the nearest archive ahead that has only its member CRC.
Without a reader's whole result the scan hashes an archive it needs
whole itself, in steps, like a file over the cutoff.

An archive with no CRC for its first entry, such as a zip or 7z with
a directory first, skipped every database, so its own CRC was never
compared.  It now goes straight to the whole walk, and readers always
hash it whole.  In first-match mode the walk's early exit leads to the
whole walk too, while that has not run.

The CRCs, sizes and walk are cleared at each file's start.  A file
that errored left them to the next, and an archive after it was
looked up by the stale CRC.  The serial is cleared with them: an
archive matched after a file that errored in its serial lookup was
written under that file's serial.

Every database's member CRC is now checked before any whole CRC;
each database used to check the whole CRC first.  That differs only
when a zip's first member matches a cartridge record and the whole
zip an arcade record.  The cartridge record now wins, written for the
zip itself where the core gate lets the zip's name into the cartridge
database, otherwise through the expansion.  With "Scan Inside
Archives" off nothing expands, so in that second case the arcade
record still wins.

* samples/tasks/database: write an rdb count over 127 as a uint

A record count from 128 up is written as a msgpack uint32.  The
read-ahead lanes size their sets from the reader window, 64 files per
reader, so they write 131 to 259 records; a fixint byte past 127
decodes as a map or a bool and the database is rejected on two- and
three-core hosts.

* scan: keep a refused crc index build refused

A database whose crc index build is refused is flagged, and neither
the size probe nor the CRC lookup starts another build for it.  The
index budget only shrinks, so a retry could only walk the database
again to the same refusal, once per content file.

The database scan test gains a cap on index builds and a lane that
scans plain files with every build refused: one query walk each.

* scan: look archives of several members up by either CRC

An archive whose header lists more than one entry, an arcade set as a
rule, is read whole first, and each database takes its own CRC or its
first member's.  A set whose first member is a cartridge ROM is
written for its arcade record rather than expanded into the cartridge
playlist, and an arcade folder walks no database claimed only for a
member's extension.  Readers read such an archive whole without a
grant.  The member pass stays for one-member archives, where a
cartridge zip needs no whole read.

file_archive_get_file_crc32_size_and_ext() reports the entry count
the backends already take from the archive's header.  A database
claimed only for the member's extension joins the member pass only
while it has or may get a crc index; unindexed, the lookup is a full
walk the expansion repeats.  A reader's member CRC survives a whole
read that fails or is over the cutoff, so the scan reads only the
archive itself.

Lanes: an arcade set with a cartridge ROM first, an arcade folder
beside a cartridge database, sets whose reader reads fail, and
cartridge zips against an unindexed database.

* archive: read a mapped zip's directory in place

When the archive is mapped, zip_parse_file_init() finds the end of
central directory record, reads the ZIP64 records and walks the
central directory where they lie; only the context is allocated.
Unmapped archives read them through the stream.  The scan's member
pass opens every zip for its directory, so this is its hot path.

archive_zip64_test counts reads through the stream and checks that,
mapped, a plain zip with a comment and a ZIP64 one give their first
member's CRC without any.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-28 23:36:20 +01:00
LibretroAdmin 3f541d5b38 rchd: CD frame geometry for version 3/4 images, constant ECC tables
Two things beetle-saturn carried in a private copy of rchd, done here
instead so every core reads CHDs with the same file.

Version 3 and 4 images store no unit size, and rchd inferred one only
from hard-disk metadata, so a v3/v4 CD image reported its hunk size
as its unit. Its tracks then did not fit and the open failed. Follow
MAME's guess_unitbytes(): hard-disk metadata first, then any CD or
GD-ROM track metadata (CHCD, CHTR, CHT2, CHGT, CHGD) means a 2448-byte
CD frame, else the hunk size. beetle-saturn papered over this with a
caller-supplied unit hint, which is no longer needed.

The sector ECC tables were filled on first use behind a plain flag,
which two decoders on two threads could race on; beetle-saturn added a
call to fill them before starting its workers. They are now constant
arrays, so there is nothing to set up and nothing shared is written.

Checked: a version 4 CD image built from a chdman image's raw data and
track metadata failed to open before and now opens with a 2448-byte
unit, two tracks, and every frame identical to the version 5 image it
came from. Images whose sectors had valid ECC (so chdman stripped it
and rchd regenerates it) read back byte-identical with cdlz, cdzl,
cdzs, cdfl and the default codecs, before and after, under ASan/UBSan.
2026-10-28 21:50:15 +01:00
Codex 79124b852a Keep hardware restart harness isolated from real GPU drivers
Use null-driver callbacks for the synthetic OpenGL restart and restore the cached video selection before leaving the test. Assert that the null driver is restored so later lanes cannot inherit a forced GPU backend.

Line-buffer harness output so a crash does not hide the last completed lane.
2026-10-28 20:57:46 +01:00
Codex 05851e980b Document the C90 string literal limit
Require the 509-character limit after literal concatenation, including macro-expanded message text, and explain how to handle longer text without suppressing diagnostics.
2026-10-28 20:31:34 +01:00
Codex b34ac23b7c Keep Game Mode help within the C90 string limit
Keep the Game Mode explanation under 509 characters so the MSVC6 stand-in compiles with pedantic errors enabled. Align the registry harness deadzone refresh with its loop scope.
2026-10-28 20:27:13 +01:00
LibretroAdmin fed1f6f7fa rchd: say how long lent bytes have to live
rchd_feed_borrow said lent bytes had to outlive every rchd_read_step()
that returned RCHD_PENDING. The decoder is done with them sooner: once
it has decoded the hunk, which is before it asks for the next range.
Spell that out, so a caller without a mapping can lend one staging
buffer per request and refill it for the next instead of paying for
rchd_feed_at()'s copy, and say that lent bytes are used once.

Documentation only; the decoder already behaves this way.
2026-10-28 20:20:01 +01:00
LibretroAdmin f93b10054e rchd: allocate nothing per hunk once an image is open
Reading a CHD allocated on the hot path. Every hunk fetched from the
file malloc'd a fresh staging buffer for its compressed bytes and freed
the last one; every CD FLAC hunk built a FLAC decoder (four
allocations) and tore it down; and the decoded-hunk cache, the CD
codecs' sector scratch and rflac's rewind point were all allocated on
the first read rather than at open. For an emulator core each of these
lands in the middle of a game.

- The staging buffer is kept and reused. It is sized to one hunk when
  the open finishes (a compressed blob never exceeds a hunk) and only
  grows for a blob that does. A borrowed supply points at the
  caller's memory without freeing it.
- A borrowed range is handed out once: a later request for the same
  range collects it again instead of reusing a pointer the caller only
  promised for one read.
- One CD FLAC decoder per image, made at open and rewound with
  rflac_reset() per hunk.
- The hunk cache and the CD sector scratch are allocated at open. Each
  falls back to allocating on first use if that fails.
- rflac: a headerless decoder allocates its rewind point with itself,
  not on its first frame.

Checked with a malloc counter over every sector of CD LZMA, CD FLAC
and mixed images at the default and 1 MiB hunk sizes: 299 and 25
allocations during reads before, 0 after. rchd_supply_test and
rchd_read_test pass under ASan/UBSan with leak detection on 23 chdman
images (four CD codecs plus the default set, hunks of 19584, 523872,
526320 and 1047744 bytes), exercising both the borrow and the copy
paths.
2026-10-28 20:19:57 +01:00
LibretroAdmin ac694282b8 Linux Game Mode: hold the performance profile only when plugged in
Like the Windows Low-Latency Power Plan, the performance power profile
now only applies on AC power. The Game Mode helper stays alive while
Game Mode is on and follows the power source:

- UPower's OnBattery property, re-read on each PropertiesChanged signal
  UPower sends when the charger is plugged in or pulled;
- without UPower, /sys/class/power_supply (a mains supply offline, or a
  discharging battery with no mains supply listed), looked at again
  every 5 seconds since nothing announces the change.

Pulling the charger releases the hold, plugging back in takes it again.
The I/O priority part costs no power and stays on either way.

The helper waits in poll() on the bus socket and a wake pipe, so it
costs nothing between changes; turning Game Mode off writes one byte to
the pipe and the helper releases, closes and exits. Hand-over is still
the one atomic word (want + busy, CAS only), no locks. Leaving on exit
still starts nothing: the hold goes with the process's bus connection.

samples/frontend/gamemode gains fake_upower (answers OnBattery, flips
it and signals on SIGUSR1) and two lanes: "plug" (hold on AC, release
when pulled, hold again when plugged back in) and "unplugged" (no hold
until plugged in).
2026-10-28 19:31:59 +01:00
LibretroAdmin 7e6deceb07 Linux: implement Game Mode natively, drop deps/feralgamemode
Game Mode no longer dlopens Feral's libgamemode and asks gamemoded to
act for us. Of what gamemoded does with its stock configuration, two
things matter to RetroArch, and both can be done from inside the
process without root:

- I/O priority best-effort level 0 on every thread (gamemoded's
  ioprio=0 default). ioprio_set() acts on one thread, so every thread
  in /proc/self/task gets it; threads created later inherit it. The
  previous priority is put back on leaving.

- The CPU in its performance mode (gamemoded's governor and platform
  profile). This is now a "performance" HoldProfile on
  power-profiles-daemon (or tuned-ppd, same interface) over the system
  bus, which desktop polkit policy grants an active session without a
  password; the pre-0.20 net.hadess name is tried as a fallback. ppd
  drops the hold by itself when our connection goes away, so a crash
  never leaves the machine stuck in performance mode.

Screensaver inhibit, gamemoded's other default, RetroArch already does
with Suspend Screensaver. The rest of gamemoded (GPU clocks, split-lock
sysctl, X3D mode, core parking, renice, custom scripts) is opt-in there
or needs root, so it is not carried over.

The D-Bus exchange runs on a short-lived detached helper through the
existing libdbus run-time loader, so entering and leaving return at
once. Hand-over between helpers is one atomic word (want + busy, CAS
only), no locks. On exit only the I/O priority is restored and no
helper is started: the hold goes with the process's bus connection.

The code is plain C89, so C89 builds now get Game Mode too
(C89_GAMEMODE=no is gone). --disable-gamemode still turns it off.

samples/frontend/gamemode is rewritten: it builds gamemode_linux.c on
its own, runs a private dbus-daemon as the system bus with a fake ppd,
and checks the hold/release, the legacy name, a refusal, no daemon at
all, the I/O priority on existing and new threads, and that the exit
path sends nothing.
2026-10-28 19:25:36 +01:00
LibretroAdmin 3193e338f3 content_load harness: screenshot lane survives a descheduled check
The lane counts on a hog task spending the per-frame I/O window ahead
of the screenshot, so each check encodes one row. When the harness is
descheduled for a whole window period (a loaded 2-CPU runner), or a
signal cuts the hog's sleep short, the screenshot opens a fresh window
and finishes the remaining rows at once, failing the lane about one
run in six.

The hog now waits out the period by the clock, and a check that ran
past the period counts as late; a late run proves nothing either way
and is taken again, up to three times.
2026-10-28 19:11:03 +01:00
LibretroAdmin 61606d92b8 rchd: accept version 5 hunks up to 1 MiB
The version 5 hunk ceiling was 512 KiB, but chdman writes hunks of up
to 1 MiB (its HUNK_SIZE_MAX). Every CD image made with a hunk above
524288 bytes -- 526320 (215 frames) and up -- was refused at open.
libchdr, which rchd replaced, had no such cap. Reported against
beetle-saturn (libretro/beetle-saturn-libretro#98).

Raise the version 5 ceiling to 1 MiB. Checked byte-exact against the
source image for CHDs made by chdman 0.264 at hunk sizes 19584,
523872, 526320 and 1047744 with cdlz, cdzl, cdzs, cdfl and the default
codec set.
2026-10-28 19:01:38 +01:00
Codex 8f51d7e34a Fix stale menu framebuffer and command test fixtures
Match RGUI scratch-line storage and exact nearest-neighbor sampling in the framebuffer harness, and release the scratch allocation. Stub command_frame_start in the hardcore input fixture so input polling links.
2026-10-28 18:25:49 +01:00
LibretroAdmin ffbe75195b 7z: BCJ2's call, jump and range coder inputs streamed too
Each comes from a source of its own - a packed stream, or a coder of
one from one - into a buffer that keeps what the converter left of the
last piece; one that cannot stream is decoded whole, as all three were.
r7z_bcj2_decode_in() takes all four inputs a piece at a time, and a
branch candidate whose bit or target is not there yet waits for it
unread. It replaces r7z_bcj2_decode_piece(), which streamed main alone.

r7z_stream_test and r7z_bcj2_test take them 16 octets at a time.
2026-10-28 16:41:48 +01:00
LibretroAdmin d9268ba356 crc32: AArch64's CRC32X in three lanes
One chain of CRC32X waits on each instruction's two or three cycles;
three lanes of 1 KiB keep the unit busy, and are put back together by
moving a lane's register on past 1024 zero octets, a linear map done a
nibble at a time from a 512-octet table: crc = on(on(c0) ^ c1) ^ c2.
No PMULL needed.

tools/encodings/crc32_bench.c times it against one chain on the
hardware; crc32_test's seams take in the lanes' sizes, and pass under
qemu-aarch64 with and without the instructions in the baseline.
2026-10-28 16:41:48 +01:00
LibretroAdmin 5afa2b3d32 crc32: Ogg's CRC-32 and CRC-16/CCITT by carry-less folding
Neither is reflected, so each 16 octets byte-swapped is 128 of the
message's coefficients as they come, and the register's seed is added
to the front. One fold, of x^n mod P for either polynomial, keeps an
accumulator equal to every octet so far mod P; it and what is left are
then the table path's from a seed of 0, with no reduction of their own.
PCLMULQDQ with SSSE3 from 64 octets, VPCLMULQDQ from 256.

From 1 KiB on, 26-27 octets a cycle against the tables' 0.75 and 1.15:
Vorbis and Opus pages 25-37x, CHD hunks 16-23x. crc32_test checks both
against bit-by-bit references at every length to 1200 and alignment,
the seams, and split anywhere.
2026-10-28 16:41:48 +01:00
LibretroAdmin a43b1ea10d deflate: Adler-32 in AVX2 and AVX-512 blocks
32 octets a turn with AVX2 and 64 with AVX-512, chosen at runtime, the
weighted sum by pmaddubsw then pmaddwd; what is left goes through the
16-octet SSE2 blocks. Against those, 2.1-2.9x with AVX2 and 2.5-3.3x
with AVX-512 from 1 KiB on, 1.4-1.9x at 128-256 octets: every zlib
stream inflated or deflated, PNG's among them.

adler32_test runs every check through each width the CPU takes, and
its longest bound no longer reads past its buffer.
2026-10-28 16:41:48 +01:00
LibretroAdmin 9621339c56 r7z stream test: BZip2 round its ring, PPMd at the end of a fetch
The ring BZip2 is read into is R7Z_BZIP2_RING, which r7z_stream_test
builds at 2560 octets: fx_big_bzip2's three blocks go round it. PPMd
over 6000 octets of noise takes about as much input as it makes, so
each step runs to where it stops short of the end of what is fetched.
2026-10-28 16:41:48 +01:00
LibretroAdmin 80b3aefa35 input: a controller's sub-device sensors, from SDL's left-hand sensors
The frontend passes a core's sensor calls for a sub-device
(RETRO_SENSOR_SUBDEVICE) on to the drivers with the index kept. It
remembers which sub-device sensors the core enabled, as it does the
controller's own, so one not enabled, or whose enable no driver took,
reads 0 without asking a driver. The controller's own sensors are
handled as before, and sensitivity and orientation apply to both; the
profile's axis remap stays the controller's.

The SDL2 (2.26 and up) and SDL3 joypad drivers answer sub-device 1 from
SDL's _L sensors: a Wii Remote's Nunchuk accelerometer, the left
Joy-Con of a pair. SDL3 restores it on a reconnect as it does the
others. Every other driver already refuses an index it does not know.

samples/input/sensor_subdevice runs the wrappers as they ship.
2026-10-28 16:37:08 +01:00
LibretroAdmin 04f8e90a9a libretro.h: what sensor sub-device 1 is, and how a core tells it is there
Index 1 is the part held in the left hand - a Wii Remote's Nunchuk, the
left Joy-Con of a pair - as SDL reports those (its _L sensors), so
cores and frontends number a controller's parts alike. A core finds out
whether the frontend has the part from the enable action's result,
since a frontend without it reads 0.
2026-10-28 16:37:04 +01:00
Ryan McClelland 1d34b80a0f libretro.h: add a sensor sub-device index for composite controllers
Some controllers carry more than one sensor of the same kind on a single
port. A Wii Remote with a Nunchuk attached is one player with two
accelerometers, and adding MotionPlus makes it two accelerometers and a
gyroscope, but retro_sensor_interface addresses exactly one sensor of
each kind per port. A second port is not an alternative, because ports
denote players.

Carry a sub-device index in the upper bits of the existing ID, the same
way RETRO_DEVICE_SUBCLASS encodes a subclass into a device ID. Index 0
denotes the controller itself and encodes to exactly the values already
in use, so every existing core and frontend is unaffected.

No new environment call or capability flag is needed, because the
existing contract already describes the fallback: a frontend that does
not implement this returns false from retro_set_sensor_state_t and 0
from retro_sensor_get_input_t, both of which are already documented, and
a core that receives either answer behaves as it does today. Cores and
frontends can therefore adopt this independently of one another.

Definitions only; no behaviour changes.
2026-10-28 16:37:01 +01:00
LibretroAdmin 8f1e421733 wiiu: take in what it used of libiosuhax, drop deps/libiosuhax
RetroArch used libiosuhax for three things: opening the IOSU handle
and an FSA client in os/wiiu/main.c, the storage_usb devoptab, and the
raw SD/USB sector access libfat reads sd: and usb: through. Those now
live in os/wiiu/fs/iosuhax.c, with everything but what main.c and
libfat call made static. deps/libiosuhax is gone.

Never used, so not taken: IOSU memory and kernel read/write, the SVC
call, volume flush, FSA mount/unmount (main.c always registered an
already-mounted path), and chmod, which newlib never reached - the
devoptab table had the chmod handler in the deviceData slot, which
mount_fs then overwrote. The table now has newlib's order, as libfat's
does.

No locks: every FSA call is one IPC request the iosuhax server carries
out on its own, and nothing is shared between threads once a device is
mounted, so the per-device OS mutex is gone. The one shared resource
left is a data buffer, claimed with a retro_atomic exchange; a thread
that finds it held stages through the stack instead.

No allocation per call: every call used to memalign one or two buffers,
every path was malloc'd, and every directory entry too. Requests are
now built in aligned stack buffers and paths written straight into
them. File and sector data go through a stack buffer up to 1 KB, the
shared 128 KB buffer made at mount up to that, and beyond it straight
into the caller's buffer: the server puts its reply header in front of
the data, so the 0x40 bytes in front of the stretch being read are
kept aside and put back. A large ROM read no longer allocates a buffer
its own size and copies it; the data is never copied.

Bugs fixed:
- statvfs had the device info call write 0x64 bytes into an 8-byte
  stack variable
- stat read the path after freeing it
- fstat passed the file state's address as the FSA file handle
- open checked the wrong pointer for allocation failure
- seek with an unknown origin returned with the mutex held
- the device lookup used strtok, which is not thread safe
- read reported an error as end of file; it now returns -1
- seeking from the end after writing past it used the old length

Requests are byte for byte what libiosuhax sent: both were run on
32-bit big-endian PowerPC against a recorder and compared. The data
paths were run against a fake server for every size and alignment,
with the shared buffer free and held, short reads and reads past the
end, under AddressSanitizer, with guard bytes around the caller's
buffer. A compile-matrix lane now builds the file against a stand-in
for newlib's sys/iosupport.h.
2026-10-28 14:58:24 +01:00
Rob Loach ef41fe39e7 MCP: review fixes for frames, RAM, hardcore 2026-10-28 14:56:51 +01:00
Rob Loach ef3c8db6b3 MCP: no tools while achievements are hardcore 2026-10-28 14:56:51 +01:00
Rob Loach 35d113d308 MCP: memory tools read cores without a map 2026-10-28 14:56:51 +01:00
Rob Loach 495120bf76 MCP: GET_FRAME shares SCREENSHOT's reply 2026-10-28 14:56:51 +01:00
Rob Loach 30a01d0696 MCP: GET_FRAME, the screen without a file 2026-10-28 14:56:51 +01:00
LibretroAdmin 642faf0a3f filter_golden: run the tests side by side, clear less, ignore the binaries
The video filter golden-hash suite (34 test programs, each built twice
in CI, plain and with ASan/UBSan) built and ran everything one at a
time. On a 2-core box it took 21 s plain and 55 s with the sanitizers.

- Each test is now its own make target (run_golden_<filter>), so
  make -j builds and runs them side by side; the workflow passes the
  runner's CPU count to all three golden suites.
- Every run cleared the whole 5 MB output buffer before filtering; it
  now clears only the rows the hash and the comparison read, which is
  all they ever looked at.
- The test programs (golden_<filter>) go in .gitignore, beside the
  golden_<filter>.h tables that stay tracked.

On the same 2-core box: 10 s plain and 28 s with the sanitizers. With
the sanitizers, most of what is left is Blargg's NTSC table, which the
filter builds at every create (about 40 million instructions each, 200
creates per variant, 5 variants).
2026-10-28 14:54:18 +01:00
LibretroAdmin d5b3c371f9 sdl3: decide input survival as the context drivers do; cheaper GL toggle
Follow-up to the SDL3 vsync/device reopen/input series.

sdl3_next_window_is_sdl3(), which lets the SDL3 input driver survive a
video restart, decided from the video_driver setting alone:

- When a core asks for gl, glcore or vulkan, the frontend swaps the
  setting and keeps the user's driver in cached_driver_id.  On the
  SDL3 video driver that is the common restart - loading any GL or
  Vulkan core - and sdl3_ctx_enabled() keeps it in an SDL3 window
  because of cached_driver_id, but the helper saw "glcore" and said
  no, so the input driver was torn down and rebuilt for nothing.
- For any driver that has no context driver (d3d*, metal, ...) it
  still looked at video_context_driver, so a gl_sdl3 left over in
  that setting said yes.  The SDL3 input driver then survived into a
  window that is not SDL's, with no keyboard events.

Take the driver in front and ask sdl3_ctx_enabled() about the context
it would use: vk_sdl3 for vulkan, gl_sdl3 for gl, glcore and gl1, no
for everything else.

The SDL3 GL context now reports GFX_CTX_FLAGS_FULLSCREEN_IN_PLACE, so
a fullscreen toggle calls set_video_mode on the window it has.  That
still went through the cached-context path every time: a needless
SDL_GL_MakeCurrent, a "Using cached GL context" log line, a
cached-context ack, and two swap-interval calls (the adaptive probe
and the restore), which some drivers make wait on the compositor.
When the window already existed and the context is this instance's
own, return right after the restyle.
2026-10-28 14:48:07 +01:00
Rob Loach 0f66fb59a7 sdl3: report the display's exact refresh rate 2026-10-28 14:48:07 +01:00
Rob Loach 1f91f7f876 sdl3: keep input, exclusive in place, 32-bit uploads 2026-10-28 14:48:06 +01:00
Rob Loach e2b460c617 sdl3: drop the full-slot pad retry 2026-10-28 14:48:06 +01:00
Rob Loach a6374b6928 sdl3: performance, in-place modes and pad fixes 2026-10-28 14:48:06 +01:00
Rob Loach fcc219b172 sdl3: vsync, device reopen and input fixes 2026-10-28 14:48:06 +01:00
LibretroAdmin a561589b0c dsp_filters: branch-free rounding in the int16 paths
The int16 paths round every fixed-point product half away from zero,
written as (v >= 0) ? ((v + h) >> s) : -(((-v) + h) >> s). Compilers
kept the branch in the busier loops, where it is taken on the sign of
the signal and mispredicts. The new dsp_filters/dsp_round.h computes
the same number without it, floor((v + h - (v < 0)) / 2^s), and
bitcrusher, chorus, echo, overdrive, panning, phaser, reverb and
vibrato use it.

It takes >> of a negative value as the arithmetic shift it is with
every compiler RetroArch is built with, as the equalizer's transform
and the video filters' lane code already do. A version that avoids that
too (shifting in unsigned after moving the value up by 2^63) cost an
extra add and was 8-12% slower in the light filters, so it was not
used. iir, wahwah, early reverb, tremolo and WSOLA keep their rounding:
there the new form was slower on 32-bit or 64-bit ARM.

Output is unchanged bit for bit: 3000 calls per filter of 1 to 3000
frames, with random, full-scale, square and quiet input, at 32, 44.1
and 48 kHz and five settings each (defaults, scaled down, scaled up,
zero, offset; reverb also at room size 1 and freeze), on x86-64,
aarch64 and armhf (qemu).

Time per 1024 frames against before:

               x86-64   aarch64 (qemu)   armhf (qemu)
  reverb       0.24     0.93             0.58
  vibrato      0.44     0.94             0.86
  chorus       0.51     0.95             0.82
  phaser       0.68     0.77             0.76
  overdrive    0.88     0.76             0.86
  bitcrusher   0.93     0.81             0.87
  echo         0.96     0.67             0.81
  panning      0.97     0.90             0.92

Reverb was the second most expensive filter on that path: 0.26 ms per
1024 frames on x86-64, now 0.06 ms.
2026-10-28 14:45:55 +01:00
LibretroAdmin 77b57d6c15 dsp_filters/eq: faster int16 path, same output
The equalizer's int16 path (a fixed-point FFT convolution) took 0.6 ms
per 1024 frames, against 0.06 ms for the float path; it was the most
expensive DSP filter by far. In its transform:

- The butterflies' rounding (half away from zero) branched on the sign
  of every product, and the halving between stages did the same for
  every value. Both are now branch-free, with the same results, and
  the halving vectorises.
- The overflow check took the absolute value of every result and ORed
  them together; it now tests each against the limit with one compare.
- The twiddle pointer steps instead of being multiplied out, and the
  first butterfly of each group, whose twiddle is exactly (1, 0), skips
  its multiplies.
- The imaginary part's rounding is spelled differently from the real
  part's, so GCC on AArch64 does not pair the two into a vector (moving
  both products to vector registers and back for one add and shift).

Output is unchanged bit for bit: 1600 calls of 1 to 4000 frames with
random, full-scale, square and quiet input, at block sizes 2^6, 2^8,
2^10 and 2^13, on x86-64 (GCC, Clang), aarch64 and armhf (qemu).

Per 1024 frames: GCC x86-64 0.35 of the time (0.62 ms -> 0.22 ms),
Clang x86-64 0.74, armhf 0.61, aarch64 0.91 (qemu, relative only).
2026-10-28 14:35:57 +01:00
LibretroAdmin d0b9d42edf 7z: every coder's folders streamed, BCJ2's and converter chains too
The stream is a source and what comes after it. The source is the
folder's first coder - LZMA and LZMA2 through their windows, PPMd,
Deflate, Deflate64, BZip2 a block at a time, or Copy - read a chunk at
a time with what the last left kept in front. Up to three branch
converters or Delta follow it, each passing on what it converts; and a
BCJ2 folder takes its main input from its source a piece at a time,
the call, jump and range coder inputs decoded whole first.
r7z_bcj2_decode_piece() is the converter over pieces of main.

What is made and not yet handed out waits for the next entry, so
in-order entries still decode their folder once.

r7z_filter_fixtures.h gains 7-Zip 23.01's archives through PPMd, BZip2,
Deflate, Deflate64, BCJ2 and Copy; r7z_stream_test streams them, built
to make 4 KiB and read 256 octets a step. r7z_methods_test and
r7z_bcj2_test stream their 7-Zip archives too.
2026-10-28 14:35:06 +01:00
LibretroAdmin 22bb0aefd2 crc32: AVX-512 VPCLMULQDQ folding
Four ZMM accumulators, 256 octets a turn, then one 64 at a time, its
four lanes folded into the 128-bit tail the PCLMULQDQ path ends with.
Chosen at runtime where AVX-512 is and CPUID leaf 7 has VPCLMULQDQ,
built with GCC 8, clang 6 and Visual Studio 2019 on x86-64.

From 512 octets on, 26-37 octets a cycle against PCLMULQDQ's 10.5-11:
2.5x at 512, 3.4x at 4 KiB, 2.8x past the caches. crc32_test's fold
boundaries take in the new seams.
2026-10-28 14:35:06 +01:00
Davey Hughes 9c20573bc3 CI: build an aarch64 Linux binary with OpenXR (#19917)
A job on the ubuntu-24.04-arm runner builds RetroArch with OpenXR and
uploads the stripped binary, so aarch64 testers, such as Steam Frame
users, can download a build of any commit or pull request.

The apt cache key now carries the runner's architecture off x86-64, so
an arm job never restores an amd64 entry; x86-64 keys are unchanged and
their caches stay warm. When a download stalls, apt-install.sh retries
architectures other than amd64 and i386 from ports.ubuntu.com, since
archive.ubuntu.com carries no arm64 packages.
2026-10-28 14:35:53 +01:00
LibretroAdmin 7c16e4e49d vfs: do not free a network pool still in use at shutdown
Follow-up to "SMB/NFS: shut the connection pools down after the core
is unloaded".

That change moves smb_shutdown() and nfs_shutdown() after the core's
deinit, so a core closing its share files on unload no longer closes
them on a freed connection.  Both functions still free the pool
whatever holds it, though, and two holders can outlive that point:

- An SMB stream holds its slot for its whole lifetime, so a file the
  core (or anything else) never closed still points into the pool.
- The exit drains tasks for at most three seconds before the core is
  deinited.  A task blocked on a dead share is still running when the
  pool goes, and frees it out from under its in-flight call.

Unpublish the pool first, so nothing new can take a slot from it, and
free it only when no slot is held, the same rule the pools already
follow when they are replaced.  If a slot is still held the pool is
left alone; the process is exiting and the OS reclaims it.

The shutdown stays where that change put it, before
frontend_driver_deinit(): the Switch tears its sockets down there,
and freeing the pool sends the SMB disconnect over them.
2026-10-28 14:26:30 +01:00
WizzardSK 761edac2d9 SMB/NFS: shut the connection pools down after the core is unloaded
retroarch_main_quit_finish() freed the SMB and NFS pools before
main_exit() deinitialised the core, so a core that closes files it has
open on a share in its unload or deinit (Vita3K, through FILE* streams
over the VFS) closed them on a freed connection: SIGSEGV in
retro_vfs_file_close_smb() on quit.
2026-10-28 14:26:29 +01:00
LibretroAdmin 1995c61dcc cc_resampler_int16: share kernel ends, skip the polynomial past the clamp
The integer CC resampler (the s16 audio path's "cc") worked out each
output frame's four kernel taps from eight evaluations of the
fixed-point cosine integral, each four rounded multiplies.

- Neighbouring taps share an end (tap i is int(x + 0.5) - int(x - 0.5)
  at x = distance + 1 - i), so five evaluations now make the four
  taps.
- The integral's polynomial is clamped to +-0.5, and past |u| = 1 the
  clamp decides: from 1 + 2048 ulp up to 10.6 every Q24 value comes out
  exactly +-0.5 (checked value by value), so those return at once. The
  outer taps land there, so most evaluations skip the polynomial.

Output is unchanged bit for bit: 36000 calls at ratios 0.3 to 1.8,
varied per call as rate control does, with random and full-scale
input, on x86-64, aarch64 and armhf (qemu).

Beyond 10.6 the polynomial's products overflowed int64 (undefined
behaviour), which happened for a while after a large ratio jump (slow
motion on, then off, at a downsampling rate). Those now return the
clamped +-0.5 the polynomial is meant to reach; that is the only case
whose output can differ.

800 frames per call, GCC -O2:

  ratio     x86-64   aarch64 (qemu)   armhf (qemu)
  1.0       0.45     0.25             0.38
  1.088     0.57     0.23             0.36
  0.919     0.59     0.24             0.39
  0.5       0.69     0.93             0.89
2026-10-28 14:25:45 +01:00
LibretroAdmin ed0757a59d libretro-common/audio_mix: NEON, SSE2 on MSVC, faster SSE2
audio_mix_volume (mix a buffer into another at a volume) is not called
in RetroArch but is part of libretro-common, which cores use.

- SSE2 is now used wherever the whole build has it, MSVC included (it
  does not define __SSE2__, so it got the C loop), as the other
  libretro-common files already do.
- ARM builds with NEON get a NEON version; the C loop was not
  vectorised there at -O2, as it may alias.
- The SSE2 version kept its 16 samples in arrays of vectors, which
  GCC at -O2 put through memory; it now works on 8 samples a step
  in registers, then 4, then one by one.

At 2048 samples (GCC 13, -O2, x86-64): SSE2 0.40 us -> 0.18 us; the C
loop is 0.68 us.  Sums match the C loop's bit for bit on x86-64,
aarch64 and armhf (qemu), for lengths 0 to 70 at odd offsets.
audio_mix_volume_C and audio_mix_volume_SSE2 keep their names;
AUDIO_MIX_NO_SIMD builds the C loop alone.
2026-10-28 14:16:50 +01:00
LibretroAdmin 4589864542 ntsc_crt: encode the signal on the workers, fix a race between bands
Each frame used to be encoded into the NTSC signal, and the noise added,
on the calling thread before the workers started: about 0.47 ms of a
1.1 ms frame at 256x224 with AVX2 on two threads, so more threads
gained little.  Now only the setup is done there (the input conversion,
each line's sync and burst, the frame's parameters: 0.06 ms).  The rest
is a list of tasks the workers take in order as each comes free:

  - 32-line bands, each encoding its video lines into their rows of
    the signal and adding the noise to those rows (the noise generator
    is jumped to each band's first sample, as the lane code already
    did, so every sample gets the same number);
  - the line-ordered start of decoding (sync search, colour carrier),
    once every band is encoded;
  - 32-line bands of decoding, each copying its output rows.

A task waits (lock-free, on retro_atomic) only for tasks already taken,
which their workers are running, so the work cannot stall with any
number of workers, nor when the packets are run one after another: a
single worker just does every task.  A worker that wakes late also no
longer holds up the frame, since the others take its share.  Each
worker has its own encoder lane buffers, made at create.  Without
lock-free atomics, the calling thread does the encoding as before.

Race fix: when the picture has more lines than output rows (224 rows
from 240 lines, say), a line with no rows of its own still draws one,
the next line's first, and that line draws over it or blends with it.
Where a band boundary fell after such a line, two workers wrote the
same row at once, and the output depended on timing.  It happened in
the existing code at some thread counts (at 256x224 with 9 workers, for
one).  Bands now never end after such a line.

Output is unchanged byte for byte from the code before this change run
on one thread: checked over 7 sizes, XRGB8888 and RGB565, plain C, AVX2
and AVX-512, 8 frames each (field, noise and carrier state carry
across them), 1 to 16 workers, with the packets run in order, in
reverse order and on concurrent threads; also with the fallback
without atomics, on aarch64 under qemu, and under ThreadSanitizer.

At 256x224, 2 workers (2-core box): 0.81-0.94 of the time with plain
C, 0.86-0.88 with AVX2, 0.77-0.81 with AVX-512; the gain grows with
the worker count, as the part left on the calling thread went from
0.47 ms to 0.06 ms.
2026-10-28 14:02:16 +01:00
LibretroAdmin 3e4d9ae7dc rgui: upscale by whole factors exactly
The upscale mapped output pixels to source pixels through a 16.16
ratio rounded down, (fb_width << 16) / out_width.  At any factor that
is not a power of two the ratio comes out short, so the first source
column and row are repeated once too often and the last ones once too
few: at 3x, column 0 is three pixels wide plus one and the last column
is two, and the drift moves every column boundary between them.  It
shows as uneven text and uneven borders at Internal Upscale 3x, 5x, 6x
and 7x, and at the Auto level whenever it lands on one of those.

The source of each output pixel is now floor(dst * fb / out), stepped
along each row and column with a remainder, so there is no division
and nothing can overflow.  At a whole factor each source pixel is
repeated exactly that many times.

Output at 2x, 4x and 8x is unchanged byte for byte; at other factors
it now matches an exact reference (checked over 1500 random sizes,
factors and pitches, under ASan and UBSan).
2026-10-28 13:37:51 +01:00
LibretroAdmin 9e4aabacc5 rgui: draw text from row masks, build each upscaled row once
Text: every glyph pixel was a bool to test, so each character cost 50
(5x10) to 100 (10x10) branches, set or not.  The font LUTs now also
hold each glyph row as a bit mask (bitmapfont_lut_t.rows, built by the
loaders from the bool LUT, which stays for the other users), and the
blits walk only the set pixels: each mask is taken 5 bits at a time
through a 32-entry table of set columns, in ascending order, so pixels
and shadows are written in the same order as before.  No compiler
builtins.  The eight blit variants share two helpers for the glyph.

Upscale (Menu > User Interface > Internal Upscale): the source column
of each output column is worked out once per redraw instead of per
pixel, and each output row is built once into a line buffer and copied
to every output row that repeats it.  The output can be a buffer loaned
by the driver (upload heap, dumb buffer), so it is written row by row
and never read back.  The line buffer is allocated before the loan is
taken, grown only when the output gets wider, and freed with the
upscale buffer.

Output is unchanged byte for byte.  Text, 22 lines of 40 characters:

               x86-64     armhf (qemu)  aarch64 (qemu)
  5x10          0.47       0.54          0.67
  5x10 shadow   0.56       0.49          0.63
  10x10         0.37       0.61          0.51
  10x10 shadow  0.41       0.44          0.46
  6x10          0.37       0.57          0.57
  6x10 shadow   0.57       0.52          0.57

Upscale of 320x240, 2x to 6x: 0.27-0.39 of the time on x86-64 (at 4x,
460 us down to 145 us), 0.26-0.58 on armhf and 0.22-0.51 on aarch64
(qemu, relative only).

Tested against the old code with standalone harnesses: all ten blit
functions on random glyphs (blank, full and mixed rows, so shadows
overlap the next glyph) and random UTF-8 text across every font range,
40000 blits; the upscale loop over 600 random sizes, integer and
non-integer factors and padded pitches, under ASan and UBSan.  Changing
the walk order, run step or line reuse makes them fail.  Builds clean
with GCC (gnu99 and gnu89 with -Wdeclaration-after-statement, with and
without HAVE_LANGEXTRA) and Clang.
2026-10-28 13:37:47 +01:00
LibretroAdmin 6a1cd6a33e video_filters: Super 2xSaI, SuperEagle and Scale2x vector runs, threads for the rest
Super 2xSaI and SuperEagle now copy blocks of one colour a vector at a
time, as 2xSaI does: a run writes a vector of pixels doubled on both
output rows, and the full rule runs on just the pixels whose block is
not one colour; after vectors that copy nothing, the plain loop takes
the next stretch, so detail costs what it did.  For Super 2xSaI, as for
2xSaI, the pixel after and the one below already settle it (its rules
then only produce those pixels and blends of each with itself); for
SuperEagle all four pixels of the block are tested, since one of its
outputs blends in the fourth.  The runs (SSE2, NEON; AVX2 picked from
the SIMD mask) and the row loop around them move into
gfx/video_filters/softfilter_copyrun.h, which 2xSaI now uses as well.

Scale2x: each output corner is the pixel above or below or the pixel
itself, chosen by equality tests, so whole vectors of pixels are now
worked out branch-free, with the first and last pixel of a row one by
one.  It also takes the workers it is given: rows are split between
them, and the rows above and below are clamped against the frame, not
the band.

Grid2x, Grid3x, Gameboy3x, Dot Matrix 3x, Normal2x and its width- and
height-only variants, Scanline2x, Upscale 1.5x and Upscale 1.66x Fast
take the workers they are given too: each output row (for the
upscalers, each group of 3 or 5) comes from its own source rows alone,
so the rows or groups are split between the workers.  They were held
to one on a note that they were not thread-safe.  The fixed-size
upscalers to 320x240 stay as they are.

Output is unchanged byte for byte.  On one thread at 256x224 (GCC):

  Super 2xSaI  game 0.85-0.93 of the time, flat 0.21-0.44, RGB565
               gradients 0.87; noise and XRGB8888 gradients within 2%
  SuperEagle   game 0.67-0.73, flat 0.14-0.27, RGB565 gradients 0.88;
               noise and XRGB8888 gradients within 5%
  Scale2x      0.25-0.59 with SSE2, 0.17-0.37 with AVX2, every frame

Tests: samples/gfx/filter_golden gains tables for the eleven filters
gaining threads, recorded from the code before this change (each is now
also run with two to four workers against one), and builds of Super
2xSaI, SuperEagle and Scale2x without their vector code.  NEON was run
under qemu on aarch64 and armhf.
2026-10-28 13:15:03 +01:00
OpenSauce04 d12ff1dda7 apple: Fix mishandling of iOS/tvOS simulator core dylibs 2026-10-28 12:06:07 +00:00
LibretroAdmin af62210543 rsx: also restore frontend state on duped frames that draw
Follow-up to "fix(ps3): RSX - restore frontend state for CPU fallback
frames".

That change restores the frontend's RSX state for both hardware and CPU
core frames, but still skips it on every duped (NULL) frame.  A dupe
is not always empty: the menu, the overlay, the OSD/statistics text
and the widgets are all drawn on top of it, and with the menu open the
frame is presented.  Those draws then run with whatever surface,
depth, blend and vertex attribute state the core left behind.  A
core that runs at 30 fps and dupes every other frame hits this every
second frame.

Restore whenever the frontend will draw anything this frame: a core
frame, the menu, the overlay, statistics, a message or the widgets.
A bare dupe that draws nothing still skips the reset, so it stays free.

Add a check to rsx_alloc_test for a dupe under the menu (it fails
without this change), and make the test rebuild when rsx_gfx.c
changes: the test #includes the driver, but nothing told make, so
editing the driver silently re-ran the old binary.
2026-10-28 12:46:19 +01:00
jcorrea 2ef2c64e0f fix(ps3): RSX - restore frontend state for CPU fallback frames
Hardware cores share RSX state with the frontend and may submit either
hardware frames or CPU buffers. Restore frontend state for both paths
to prevent CPU fallback frames from inheriting the core's render state.

Keep the reset skipped for NULL duplicate frames.
2026-10-28 12:46:19 +01:00
LibretroAdmin e3b7bb3533 rslc: use a fresh variable per dynamic access of a local constant
Follow-up to "rslc: initialize local constants at their use sites".

That change stores the constant right before each dynamic access,
which fixes the Adreno scratch-memory blowup, but every access to the
same constant reuses one Function variable.  With two or more access
sites the variable is stored again after it has already been read,
and from different blocks.  Mesa's nir_opt_large_constants only
promotes a variable to a constant table when all of its stores are
constant, come before any read and sit in one block, so the shared
variable is no longer promoted on RADV, ANV, Turnip, NVK or lavapipe
and falls back to registers or scratch -- the same class of slowdown
the change set out to fix, moved to Mesa drivers.

Emit a fresh Function variable for each access instead, stored right
before it is read.  This is exactly what glslang does with its
"indexable" temporaries: the store stays inside the branch that reads
it, and each variable is written once before its only read, so Mesa
promotes every one of them again.

Global constants are unchanged: one Private variable initialised with
the constant.
2026-10-28 12:34:20 +01:00
Li Feng d5094ca350 rslc: initialize local constants at their use sites
Keep constant-array stores inside the branches that use them instead of initializing every array at function entry. This avoids excessive scratch-memory traffic in Hyllian shaders on Adreno GPUs.
2026-10-28 12:34:19 +01:00
LibretroAdmin ed7f5aeba8 oga: views surface made at init, cleared only when the views move
The surface a core's views are laid out on was created the first time
a views frame came through, an allocation in the middle of a game, and
cleared to black with the RGA on every views frame.

It is now made at init with the other surfaces; without it, views are
drawn whole as before. The views overwrite the same rectangles every
frame, so the rectangles of the last frame are kept (at most
VIDEO_VIEWS_MAX_PLACEMENTS of them, in the driver's own struct) and
the surface is cleared only when they change.
2026-10-28 12:30:09 +01:00
LibretroAdmin 0bc6e1f6da oga: page flips, no per-frame clears, 16-bit pages, real row pitch
The ODROID-GO Advance video driver did a full drmModeSetCrtc modeset
for every frame and cleared the whole page with the RGA before every
blit. It also wrote into pages without knowing whether the panel was
still scanning them out.

Presenting: the first frame sets the mode and every frame after is a
drmModePageFlip, which the kernel carries out at the next vblank on
its own. The only wait is for the previous flip to land before the
next is queued, and the RGA work for the new frame runs before that
wait. A page is never drawn while it is on screen or queued. A driver
that refuses page flips falls back to drmModeSetCrtc.

Fast-forward and vsync off: instead of showing every fourth frame,
a frame whose flip would have to wait is skipped before any RGA work
is done for it. The panel shows one frame per refresh and the core
runs as fast as it can, threaded or not.

Clearing: the blit writes every pixel inside the picture, so only the
borders can hold anything stale. A page is cleared once after the
picture moves, the menu/game/views switch or the rotation changes,
after a message was drawn over it, or while paused (the dimming blend
has to land on black) - never as a matter of course.

16-bit pages for 16-bit cores: the scanout pages take the core's
depth, so an RGB565 core's frames go out at 16 bits. That halves the
bytes the RGA writes and the display controller reads every frame.
Menu, messages and views are still converted by the RGA on the way.

Menu: RGUI's frame is kept at its own depth and copied by rows; the
per-pixel RGB565 to XRGB8888 loop on the CPU is gone, and with it its
colour error (low bits of one channel spilled into the next). The menu
surface is made at init and only made again if a frame outgrows it, so
opening the menu allocates nothing.

Bugs fixed:
- surfaces recorded their pitch as width * bpp, but Rockchip pads dumb
  buffer rows to 64 bytes. A core whose rows do not fall on that
  boundary (a 700 pixel wide PSX frame is 1400 bytes, padded to 1408)
  was lent a framebuffer with the wrong pitch, and the RGA read each
  row from the wrong place. The kernel's pitch is used now, for the
  core's loan and for every RGA stride.
- mappings and prime fds were never released, and a failed init leaked
  everything it had made
- pages and the views surface are no longer mapped for the CPU, which
  never touches them
- the console's CRTC is put back on exit

oga_blit no longer uses designated initializers, so the file is C89.
2026-10-28 12:26:40 +01:00
LibretroAdmin 9f3414c209 video_filters: 2xSaI and Pixel Art AA vector runs, threads for the rest
2xSaI: a pixel equal to the pixel after it and the one below it comes
out as four copies of itself, whatever the fourth pixel of its 2x2
block is (the full rule's second and third cases then only produce
it).  Those pixels are now found and written a vector at a time (SSE2
and NEON, 4 or 8 pixels; AVX2, 8 or 16), and the full rule runs on just
the pixels that failed, as 2xBR does.  After vectors that copy nothing
the next 1, 3, 7 and up to 63 vectors' worth of pixels go through the
plain loop, and those stretches run in the plain loop's own form, so
detail costs what it did; builds without a vector tier keep the plain
loop alone.

Pixel Art AA: each of the four output corners is the pixel or a blend
of it with the neighbour above or below, chosen by equality tests, so
whole vectors of pixels are now worked out branch-free, with the first
and last pixel of a row (whose side neighbours clamp) one by one.  It
also takes the workers it is given instead of one: rows are split
between them, and the rows above and below are clamped against the
frame, not the band.

Normal4x, Grid4x, Gameboy4x and Dot Matrix 4x take the workers they are
given too.  They were held to one on a note that they were not
thread-safe, but each output row comes from its own source row alone.
On one thread they are bound by writing 4x the pixels out; copying
repeated output rows, tried as well, was slower than writing them.

2xBR: its backoff carried the vectors to skip from row to row, but
restarted each row at that distance, so once it reached its cap on a
frame narrower than the skip (504 pixels), no later row of the frame
tried a vector again and none could reset it.  The skip is now carried
in pixels, running on into the next row.  2xSaI shares the same loop.

The SSE2, AVX2 and NEON gating and SOFTFILTER_FORCE_INLINE move into
gfx/video_filters/softfilter_simd.h, used by 2xBR, 2xSaI, De-Dither and
Pixel Art AA.

Output is unchanged byte for byte.  On one thread at 256x224 (GCC):

  2xSaI     game 0.61-0.63 of the time with SSE2, 0.60-0.62 with AVX2;
            flat 0.12-0.20; RGB565 gradients 0.75; noise and XRGB8888
            gradients within 3%
  Pixel Art 0.16-0.30 with SSE2, 0.13-0.19 with AVX2, every frame type;
  AA        0.85-0.97 without a vector tier
  4x        unchanged on one thread; two workers about 0.55-0.7 of the
            time, where they could only use one

Tests: samples/gfx/filter_golden gains tables for Pixel Art AA and the
four 4x filters, recorded from the code before this change, and an
output buffer large enough for 4x; Pixel Art AA and 2xSaI are also
built without their vector code.  NEON was run under qemu on aarch64
and armhf.
2026-10-28 12:17:45 +01:00
LibretroAdmin e6e0404029 go2 drm context: take in what it used of libgo2, drop deps/libgo2
Only one file in RetroArch ever used libgo2: the ODROID-GO Advance GL
context, gfx/drivers_context/drm_go2_ctx.c. The oga video driver
already talks to librga and DRM itself, and libgo2's audio, input,
backlight, PNG and queue code was never called. What the context used
- open the panel, make the EGL/GBM surface, and have the RGA scale and
rotate each frame into a portrait buffer for the CRTC - now lives in
the context itself, and deps/libgo2 is gone.

No thread, queue, mutex or semaphore remains. libgo2 ran a render
thread, a mutex-guarded queue and two semaphores only to hide a full
drmModeSetCrtc modeset every frame. Frames are now page-flipped with
drmModePageFlip, which the kernel already does asynchronously, so the
only wait left is for the previous flip to land before the next is
queued - the vsync throttle itself - and the RGA work for the new
frame runs before that wait instead of after it. That is also one
frame less latency than the old three-deep queue. A driver that
refuses page flips falls back to drmModeSetCrtc.

Overhead taken out of the frame:
- the whole-screen RGA colour fill before every blit is gone; the
  borders beside the picture are cleared once per buffer when the
  picture's size or position changes, and never when it fills the panel
- each GBM buffer's prime fd is exported once and kept on the buffer
  (gbm_bo user data) instead of a per-frame search of a malloc'd list
- the scanout buffers' fds, strides and formats are worked out at init,
  not per blit
- nothing is allocated per frame

With vsync off (swap interval 0, e.g. fast-forward) a frame whose
flip would have to wait is not shown rather than waited for, so the
core runs uncapped without tearing; before, the interval was ignored.

The EGL context now comes from egl_common like drm_ctx's, so
egl_bind_hw_render works on it (a hardware core gets its shared
context) instead of being handed an empty egl_ctx_data_t. It is still
GLES 3. Fixed on the way: the context data was never freed, prime fds
were leaked, and the console's CRTC is put back on exit.

-lpng and -lz are no longer linked for HAVE_ODROIDGO2; only libgo2's
unused PNG writer needed them. A compile-matrix lane now builds the
context against the librga stand-in; nothing compiled it before.
2026-10-28 12:13:29 +01:00
LibretroAdmin e9db61132a 7z: folders with a branch converter or Delta streamed
A folder of LZMA or LZMA2 and a converter after it - BCJ, ARM, ARMT,
ARM64, PPC, SPARC, IA64 or Delta - streams through its window as one of
LZMA alone does. Each piece is copied out of the window, which matches
still read unconverted, and converted there; the octets of an
instruction not yet seen to its end wait for the next piece, and the
folder's last pass as they are. Two converters in a row are decoded
whole, as before.

r7z_filter_fixtures.h holds 7-Zip 23.01's archive of each, over a
dictionary of 4 KiB; r7z_stream_test streams their members in and out of
order, and one of x86 calls close together across every piece's end.
2026-10-28 12:02:29 +01:00
LibretroAdmin 2c6b2b55fa xxh3: a state keeps its path's take from its reset
xxh3_update() calls it straight, with no path to load and compare each
time; pieces of 257 octets to 4 KiB are taken 1-4% quicker.
2026-10-28 12:02:29 +01:00
LibretroAdmin 730feb360e core options: keys found in slots past 64 options
GET_VARIABLE and SET_CORE_OPTIONS_DISPLAY looked through every option
for the key. Past 64 options the keys are put in slots by their hash
when the options are made, and probed; up to 64 the walk in order is
quicker and stays. A lookup among 400 options takes 87 cycles, not 239;
among 200, 53 for 151.
2026-10-28 12:02:29 +01:00
LibretroAdmin a2cd920563 CI: Linux griffin unity build
make -f Makefile.griffin platform=unix, on every push and pull request.
2026-10-28 12:02:29 +01:00
LibretroAdmin c423961000 2xbr: copy runs of flat pixels a vector at a time
Most of a game frame is pixels 2xBR only copies: a pixel equal to
enough of its four direct neighbours that none of the corner rules can
fire.  The flat-block test already skipped the rest of their
neighbourhood, but still cost five loads, four comparisons and four
scalar stores a pixel, about 6 cycles.  Now that test runs on 4 or 8
pixels at once (SSE2, NEON; 8 or 16 with AVX2), every pixel of the
vector is written doubled on both output rows, and the full rule runs
on just the pixels that failed it, whose own output then replaces the
copy: the same result the per-pixel path gives.

On detail, where no pixel in a vector is copied, the vector work would
be wasted, so after such a vector the next 1, 3, 7 and up to 63 vectors'
worth go one by one, carried from row to row.  The full rule is forced
inline in this path; as a call, the loop around it cost detailed frames
several percent.  Builds with no vector tier keep the previous per-pixel
loop exactly, as the reworked one is slower there.

SSE2 is used where the build has it (all x86-64), NEON on ARMv8 and ARM
builds with NEON, and AVX2 is picked at create from the SIMD mask, gated
like the other filters' tiers.

Output is unchanged byte for byte.  On one thread at 256x224, under GCC
with SSE2 a game frame takes 0.71 (RGB565) and 0.80 (XRGB8888) of the
time it did and a flat one 0.35 and 0.56; with AVX2 0.74, 0.78, 0.33
and 0.49.  Noise and smooth gradients, where nearly nothing is copied,
take the same time as before (within 2%), under Clang too.

Tests: 2xBR moves into samples/gfx/filter_golden, recorded from the
code before this change, where it is also run with the host's SIMD
mask and built without its vector code; samples/gfx/xbr_filter_golden,
which only did the first, is retired.  SOFTFILTER_NO_SIMD now leaves out
the vector code of every filter that has any (2xBR, De-Dither, and SNES
NTSC as well as its own SNES_NTSC_NO_SIMD), and the harness builds each
of them that way too.  NEON was run under qemu on aarch64 and armhf.
2026-10-28 11:59:45 +01:00
LibretroAdmin 2158d80e7b dedither: worker threads, and SSE2, AVX2 and NEON runs
De-Dither forced itself onto one worker whatever RetroArch asked for,
and spent about 28 cycles a pixel in a chain of short-circuited colour
comparisons whose branches game content mispredicts.

It now takes the workers it is given: each gets a band of rows, and the
6x6 pattern is bounded by the frame's height, not the band's, so the
output does not depend on how many there are.  The interior of each row
goes through a vector run that works out all ten comparisons of 4
(SSE2, NEON) or 8 (AVX2) XRGB8888 pixels, or 8 or 16 RGB565 ones, at
once and branch-free, with the plain per-pixel code for what is left of
a row; the edges are copied as before.  SSE2 is used where the build has
it (all x86-64), NEON on ARMv8 and ARM builds with NEON, and AVX2 is
picked at create from the SIMD mask, gated like the other filters'
tiers (target attribute on GCC 5+ and Clang 4+, MSVC 2017 15.3+, the
baseline only under PlayStation clang and clang-cl).

Output is unchanged byte for byte.  On one thread at 256x224 a game
frame takes 0.17 (RGB565) and 0.30 (XRGB8888) of the time it did with
SSE2, and 0.11 and 0.16 with AVX2; noise in XRGB8888, where the old
code bailed out early, 0.77 and 0.40.  More workers divide that
further.

The golden harness gains De-Dither, and its game frames a band of rows
alternating between two colours: until now nothing in them was dither
that runs down but not across, so De-Dither's RGB565 vertical path (and
any filter's handling of it) went untested.  Every table is re-recorded
for the new frames, from the code before this change for De-Dither and
from current master, which matches the earlier recordings byte for
byte, for the rest.  NEON was run under qemu on aarch64 and armhf.
2026-10-28 11:34:27 +01:00
LibretroAdmin 2fab49d004 phosphor2x: build each line pair in one pass
A Phosphor2x line went through five sweeps of the output: the source
splatted to every other pixel, the pixels between blended in, then the
red, green and blue phosphor bleeds each read-modify-writing the line in
place, then the scanline below read back from it.  Every output pixel
only depends on the stretched pixel just before it, so each is now built
in a register with the same set_ macros, the scanline pixel from it, and
both lines are written once.

The work is done in 32 bits: RGB565 values stay within their 16 there,
and GCC's 16-bit register forms for uint16_t cost a merge on each
operation, which is why the RGB565 path took twice as long as XRGB8888
under GCC (Clang already avoided it).

Output is unchanged byte for byte (32-bit x87 builds, whose float
tables differ from SSE builds, give the same bytes as before too).  At
256x224 on one thread under GCC, a frame takes 0.53-0.82 of the time it
did in RGB565 and about 0.9 in XRGB8888.
2026-10-28 11:34:17 +01:00
Codex 37d4d82108 Include SIMD capability definitions in SNES NTSC implementation
The standalone translation unit selects AVX2 blitters using the softfilter capability mask. Include its defining header directly so PS5 compilation does not rely on the wrapper inclusion order.
2026-10-28 11:33:28 +01:00
LibretroAdmin 59ea3a670d blargg_ntsc_snes: SSE2, AVX2 and NEON blitters
Each output pixel of the SNES NTSC filter is the sum of six kernel
entries (twelve in hires) out of a table indexed by input colour, then
clamped and packed: 42 loads and 35 adds for every 7 pixels.  Read the
other way round, each input pixel adds a run of 14 consecutive table
entries into 14 consecutive output pixels, so a chunk of 7 output pixels
is a handful of whole-vector loads and lane blends.  The new blitters
add up the chunk that way, 4 lanes at a time with SSE2 or NEON and all 8
at once with AVX2, and clamp and pack lane-wise with the same operations
as the scalar macros.

That needs one lane per table entry, so where a vector blitter is built
the table keeps the low 32 bits of each entry instead of an unsigned
long: entries are still built at full width at create, and the
blitters only add, mask and shift them, with nothing above bit 30
reaching a pixel, so the sums give the same output.  Where unsigned long
is 8 bytes (64-bit Linux and macOS) that also halves the table, from 8
MB to 4 MB.  Builds without a vector blitter keep the old table and the
old scalar blitters unchanged.

SSE2 is used where the build has it (all x86-64; 32-bit x86 built with
SSE2) and NEON on ARMv8 and ARM builds with NEON.  AVX2 is picked at
create from the SIMD mask, built with the target attribute on GCC 5+ and
Clang 4+, as is on MSVC 2017 15.3+, and only where the baseline has it
under PlayStation clang and clang-cl.  The table gains a few readable
entries either side for the blended-away lanes that fall just outside
it.

Output is unchanged byte for byte, in every tvtype.  At 256x224 on one
thread a frame takes 0.27-0.32 of the time it did with AVX2 and
0.37-0.47 with SSE2; hires (512 wide) 0.34-0.42 and 0.45-0.56.

The golden harness gains tables for Blargg NTSC SNES (composite, and RF,
RGB and S-Video, which build different tables, RF without the field
merge), a build of it with the vector blitters left out, and a check
that a filter created with the host's SIMD mask gives the same bytes as
one created with none.  NEON was run under qemu on aarch64 and armhf:
it matches the recorded tables, and on aarch64 builds, where GCC fuses
multiply-adds while building the table, gives the same bytes as the old
code there.
2026-10-28 11:03:39 +01:00
LibretroAdmin d2db43ba2a xxh3: short pieces held up to 1 KiB, AVX-512 stores whole stripes
Pieces of up to 256 octets are held, in fixed copies that overlap, until
1 KiB is; longer ones go where they lie. AVX-512 builds the stripe the
held octets end inside in a register and keeps what is left a vector at
a time, so the next call's loads come from one store each. AVX-512 now
needs BW, from GCC 5 and Visual Studio 2017 15.3.

Against XXH3_64bits_update() with AVX-512, in pieces of 1 to 512 octets
1.13-1.86x, 700 to 1500 1.03-1.12x, 2 KiB even, 3 KiB on 1.05-1.30x.
2026-10-28 10:40:20 +01:00
LibretroAdmin f578139ead gfx_animation: ticker caches keyed by XXH64 and length
Labels of eight bytes and up hash with XXH64 and spacers with FNV-1a;
entries match on length and memcmp. A smooth ticker frame takes about
12% fewer cycles.
2026-10-28 10:40:20 +01:00
LibretroAdmin c535cc87e3 7z: a big solid folder's members streamed, and on from one another
A member out of a folder of others over 32 MiB is streamed into a
buffer of its own, and the folder never held whole. A stream that has
put out one member of a folder goes on from there for a later one, so
a folder's members streamed in order decode it once, whether to their
files or into memory.
2026-10-28 10:40:20 +01:00
LibretroAdmin 9380b249e0 griffin: the Linux unity build links
Makefile.griffin builds Linux with _GNU_SOURCE, as Makefile does, so
rthreads.c's affinity calls are declared from the first system header.
The Vulkan X context's window hints and the udev joypad's sensor
functions have names of their own, which the X context and udev input
driver's no longer clash with, and the X11 keyboard LED driver is in
the unity build where X11 is.
2026-10-28 10:40:20 +01:00
LibretroAdmin 1ea79ad400 rrar test: RAR 5 members written for it, round the ring
rar5_write.h writes a RAR 5 archive of one member, LZ symbols in codes
of fixed lengths with delta and E8 filters among them, and works out
what it decodes to. Members of 10 and 40 MB in dictionaries of 4 and
32 MiB, with matches back across the dictionary and filters' blocks
across where the ring comes round, must come out whole and streamed,
from memory and read.
2026-10-28 10:40:20 +01:00
LibretroAdmin 819eae88c6 phosphor2x: look the bleed and scanline levels up instead of computing them
Every output pixel of Phosphor2x went through float maths twice: the
phosphor bleed scaled each channel by a gamma curve, and the scanline
pass scaled each channel by a level picked from the pixel's brightest
channel, each followed by a float-to-integer conversion.  Both results
depend only on small integer inputs (a channel value for the bleed; the
brightest channel and the channel being scaled for the scanline), so
they are now worked out once at create into tables, with the same
expressions and operand types the lines used, and the lines look them
up.  The tables are about 70 KB and live in the filter's existing
allocation; nothing is allocated per frame.

Output is unchanged byte for byte.  At 256x224 on one thread a frame
takes about half the time it did, in both pixel formats and for game,
flat and noise content.

The golden harness that held the 2xSaI family is generalised so any
filter can use it: it skips input formats a filter does not take, picks
the output format and size the filter reports, and checks that 2, 3 and
4 workers give the same output as one.  It moves from
samples/gfx/sai_filter_golden to samples/gfx/filter_golden, and gains a
table for Phosphor2x recorded from the code before this change.
2026-10-28 10:24:46 +01:00
Codex 3cfa10954f Fix unmatched input conditional in Griffin unity build
Remove the extra closing directive after the UDEV input sources. It has no opening conditional and prevents every Griffin platform configuration from preprocessing.
2026-10-28 10:01:24 +01:00
LibretroAdmin 45dc0a99c3 input: Dingux rumble through evdev and the output writer; drop libShake
libShake was used by one driver, sdl_dingux, for its vibration motor,
and linked on webOS for nothing. What it did there is a few evdev calls:
find the node that plays a periodic sine, upload a sine per motor, play
and stop it, set the gain. input/common/linux_ff.h does those with no
allocation (libShake scanned /dev/input with scandir and malloc'd a node
per entry) and none of its bugs (an open that failed was taken as
success).

The uploads and plays now go out from the shared output writer, as
udev's and XInput's rumble do, so a motor's driver never holds the
frame; the frontend's thread notes the strengths, both in one word, and
returns. A core asking the same strength each frame costs no call at
all. On MIPS (-O2, mips32) that path is 15 to 19 instructions with no
stack frame, where it was about 32 with a frame and four spills.

Every SDL Dingux target gets the motor now, not only the three that
built libShake; one without a motor finds none and has no rumble, as
before.

The output writer makes no syscall for a wake while one is still on its
way, which every driver on it gains from. xinput_rumble_test says a call
did not wait by the write not being done yet, not by a time a loaded
runner can overrun.

samples/input/dingux_rumble runs the real driver against a stand-in
/dev/input and SDL, with threads and without.
2026-10-28 08:02:03 +01:00
LibretroAdmin 78e2b61746 rar: a streamed member through a ring
A member streamed out is unpacked round a ring of its window or a
filter's largest block, and two steps: once the next step would not
fit before the end, the decoder goes on from the start, and a match
reaching back before it is copied from the end and on from the start.
What is held back for a filter is read from where it lies, in two where
the ring comes round. Nothing is moved, the ring is the dictionary's
size whatever that is, and the 241 MB member of the tests streams in
two thirds of the time.
2026-10-28 07:32:17 +01:00
LibretroAdmin 60ac9365f7 rzstd: a frame a block at a time through a ring
The window a frame is decoded through a block at a time is a ring of
the window and two blocks, at least a megabyte: a block goes where it
fits whole, at the start again once it would not, and a match reaching
back before the start is copied from the end and on from the start.
Nothing is moved, and the buffer is half what it was.
2026-10-28 07:32:17 +01:00
LibretroAdmin 227706b0d7 bz2: a .bz2 listed a block at a time
Listing decodes the stream a block at a time for its size and CRC,
where the file stream lends it whole or through a ring of twice the
most a block takes. A member up to 8 MiB is kept for the extraction
that follows; a bigger one is decoded again, to its file a block at a
time. A member read to a file goes there a block at a time, and none
of one that does not decode is left behind.
2026-10-28 07:32:17 +01:00
LibretroAdmin 6cda3a9dac statestream and the PS4 shader cache hash with libretro-common's
The statestream block index keys its blocks with xxh3_64(), against
XXH32 a tenth of the time on a 16 KiB block and under half on 64 and
128 octets. The PS4 shader cache keeps its XXH64 keys, now from
xxh64_update() on the stack, so cached binaries still match. Nothing
else used deps/xxHash, which goes.
2026-10-28 07:32:17 +01:00
LibretroAdmin bb4f9ba871 xxh3: taken in pieces
xxh3_reset(), xxh3_update() and xxh3_digest() give xxh3_64() of the
pieces handed over one after another, as XXH3_64bits_update() does.
Pieces are taken a stripe at a time where they lie, whole blocks
through the one-call loops, on the path the CPU has; under 256 octets
are held between calls, and holding a piece is a copy and no more.
2026-10-28 07:32:17 +01:00
LibretroAdmin 646b31bd32 xxh64: a stripe a turn, one stripe without the loop
The lanes are bound by the multiplier however the stripe loop is
unrolled, so it takes one stripe a turn, and an input of one stripe
skips the loop: with lengths that vary call to call, 5-12% fewer cycles
than the reference inlined under 256 octets, and the same above.
2026-10-28 07:32:17 +01:00
LibretroAdmin 2f2d030223 video_filters: Super 2xSaI and SuperEagle skip their rules on flat blocks
When a pixel and its right, lower and lower-right neighbours are one
colour, both filters give all four output pixels that colour: the
block lands in the rules' third branch, whose four tie-break terms
each compare two equal colours and cancel to zero, and a colour
blended with itself is that colour, in both formats. Most of a game
frame is such blocks, yet each one paid for the tie-break, and for
Super 2xSaI the corner rules after it.

- SuperEagle checks for the flat block inside that branch and sets
  the four pixels without the tie-break. Nothing else reaches the
  check, so other content pays nothing for it.
- Super 2xSaI checks the four pixels before loading the rest of the
  4x4 neighbourhood and writes the block at once. Placed in the
  third branch instead, it saved half as much and still cost as
  much elsewhere.
- 2xSaI is left as it is: its rules already settle a flat block on
  the first test of that branch, and a check of its own gained
  little on game frames while costing up to 30% on noise.

Output is unchanged byte for byte. One thread, 256x224, time against
before (lower is faster; ranges over repeated paired runs and over
code alignment):

                  game-like    flat         noise
  SuperEagle      0.55-0.60    0.42-0.44    0.98-1.00
  Super 2xSaI     0.43-0.50    0.21-0.29    1.00-1.21

Noise, where no block is ever flat, is the worst case: there Super
2xSaI pays for the extra test on every pixel, by an amount that moves
with code layout.

samples/gfx/sai_filter_golden passes for all three filters, plain and
under ASan/UBSan, and fails if the flat path writes any other colour.
2026-10-28 07:27:55 +01:00
LibretroAdmin 43ce599231 compile-matrix: build every x86 SIMD unit for the PS4 and PS5 targets
PlayStation clang declares in immintrin.h only the intrinsics of the
ISAs a whole build enables, so a kernel under a per-function AVX2 or
AVX-512 target compiles on every host and fails on Orbis. The matrix
now finds each unit carrying a per-function target or 256/512-bit
intrinsics by content and compiles it for x86_64-scei-ps4 (AVX) and
x86_64-sie-ps5 (AVX2, no AVX-512), so a new one is checked the day it
lands. A clang without the targets reports the lane skipped.
2026-10-28 07:03:51 +01:00
LibretroAdmin 3bc6bc16ff video_filters: 2xSaI filters clamp two rows down against the frame
2xSaI, Super 2xSaI and SuperEagle read a 4x4 neighbourhood, two rows
below the current one. Each worker stopped those reads at the bottom
only when its slice was the frame's last. A worker whose slice ends
one row above the bottom, ahead of a one-row slice, read a row past
the end of the input: past the core's frame, into whatever follows
it. Rows divide among workers as height * i / threads, so this happens
when the frame has fewer than about two rows per worker - small
frames, or many filter threads.

Each worker now gets the number of frame rows below its slice instead
of a last-slice flag, and clamps against the frame. For the last
worker that is what it did before; for the others, the output changes
only where they read out of bounds. Over the golden matrix below,
one case changes (SuperEagle, a 3x3 frame in RGB565, with three and
four workers);
every other output is the same.

samples/gfx/sai_filter_golden runs each of the three filters over
game-like frames and noise, both formats, sizes from 1x1 to 320x240,
frames smaller than the created size, input rows at a wider stride,
and one to four workers. It checks output hashes recorded from this
implementation, and that two, three and four workers give one
worker's output byte for byte. It runs plain and under ASan/UBSan in
the filter plugins workflow; ASan reports the out-of-bounds read
against the previous code.
2026-10-28 07:01:17 +01:00
LibretroAdmin 4bead9073a netplay: one CRC check for both paths, decided anew after each state load
A client compares the host's CRC for a frame either when it arrives or
once the client reaches that frame, and both now go through one check.
The first check at or after a state loaded from the host decides
whether this core's CRCs can be compared: that state is the host's, so
a mismatch there turns desync checks off with a warning, as a core
whose states carry a timestamp needs. Any later mismatch asks for the
host's state. A client logs each state it loads from the host.

samples/network/netplay_e2e gains a rollback desync lane with input
latency, so some checks wait for the client to reach their frame, and
a lane whose core states never hash alike between peers, which must
stop at one resync past the join with its checks turned off.
NETPLAY_E2E_JUNK puts a per-peer serialize count in the test core's
state.
2026-10-28 06:53:39 +01:00
LibretroAdmin fb3908fd0a video_filters/ntsc_crt: noise pass in SIMD lanes via a jumped generator
demodulate_begin adds noise to all 238420 samples of the signal before
the workers start, one sample at a time: each step of the linear
congruential generator (rn = 214019 rn + 140327895, mod 2^32) waits
on the one before, a multiply and an add per sample, about 0.45 ms of
every frame.

Applying the generator k times is again one multiply and add, with
A = a^k and C = c (a^(k-1) + ... + 1), mod 2^32. So lanes can each
start at their own sample and step by the lane count, and they see
exactly the numbers the one-at-a-time loop does. With AVX2, 32
samples a step in four vectors; with AVX-512, 64 in four; four
independent vectors keep the multiply's latency hidden. The noise,
the clamp and the narrowing to bytes are the scalar loop's integer
operations. The samples left over finish in the scalar loop from the
state crt_lcg_jump computes, so v->rn, which carries to the next
frame, is unchanged too.

The tier is the one the lanes elsewhere use, from the SIMD mask given
to create; this pass needs no buffers. demodulate_begin comes before
the lane code in the file, so the tier macros and the generator's
helpers now sit in front of it.

256x224 on one thread, AVX-512 Xeon:

  demodulate_begin  0.45 -> 0.10 ms (both tiers)
  frame             3.1 -> 2.8 ms

The golden test's noise scenarios, run over four consecutive frames,
cover the carried state; it passes at all three tiers, and fails at
the wide tiers alone if the vector arithmetic is changed, or at every
tier if the jump is.
2026-10-28 06:52:45 +01:00
LibretroAdmin 57c1e67ac1 video_filters/ntsc_crt: modulate 8 or 16 lines at once in SIMD lanes
The modulator runs on the calling thread before the workers start, so
it adds to every frame whatever the worker count. For RGB input, the
format the wrapper feeds it, each line runs three one-pole filters
along its samples from zero and turns them into a signal level, and
it converted the source pixel from RGB to YIQ again for every sample
- about three times per pixel at 256 wide. That was about 1.3 ms at
256x224.

Lines are independent here, so they now run in lanes, as the decoder's
do: 8 with AVX2, 16 with AVX-512, each lane doing the scalar loop's
integer operations in its order, so the output is unchanged byte for
byte. Each batch's source rows are converted to YIQ once per pixel,
into lane-interleaved planes, a column at a time. Then the lanes run
the filters and levels per sample, and each lane's levels are copied
to its signal row. Lines left over (236 lines do not divide by 8 or
16) take the loop as before.

The planes are sized at create from the largest frame create is told
of; a wider frame, which the softfilter contract rules out, is
modulated line by line instead. The tier is the decoder's, from the
SIMD mask given to create, built per function and, under PlayStation
clang or clang-cl, only where the baseline enables it. The lane code
sits before the modulator's EXP_P definition, so it carries its own
copy, with an #error should the two ever differ.

256x224 on one thread, AVX-512 Xeon:

  modulate   1.28 -> 0.53 ms (AVX2), 0.54 ms (AVX-512)
  frame      3.8 -> 3.05 ms with either

What is left of the modulator is mostly the per-pixel conversion and
the copy back to the rows, both scalar.

samples/gfx/crt_filter_golden now names its tiers SIMD tiers, since
they cover the modulator as well; its table and matrix are unchanged
and it passes at all three.
2026-10-28 06:30:01 +01:00
LibretroAdmin 4c4bbd5a49 video_filters/ntsc_crt: decode 8 or 16 lines at once in SIMD lanes
After demodulate_begin, each active line is decoded on its own: three
equalisers, each a chain of IIR stages, run over every sample, every
step waiting on the one before. That was about 4.7 ms of a 256x224
frame on one thread.

Lines are independent, and without bloom (as built) every line covers
the same samples, so they can run in lockstep: one line per lane, 8
with AVX2 or 16 with AVX-512. Each lane does exactly the scalar
decoder's integer operations in the same order - the multiplies keep
the low 32 bits and the shifts are arithmetic, as int arithmetic is on
every target this builds for - so the output is unchanged byte for
byte.

- The lanes' signal is transposed to one byte per lane per sample, and
  Y, I and Q are decoded in separate passes so each pass's state stays
  in registers.
- The decoded samples stay interleaved, and the pixel pass, now
  crt_render_line, reads them through a stride. The line-by-line
  decoder passes its struct crt_yiq array the same way, so both share
  one pixel pass.
- Lines are taken in order, a batch at a time; what is left over (fewer
  lines than lanes) is decoded line by line, so rows are drawn in the
  same order as before.
- The tier comes from the SIMD mask RetroArch passes to create - the
  plugin calls nothing of its own to detect the CPU - with
  SOFTFILTER_SIMD_AVX512 added to softfilter.h as RetroArch's
  RETRO_SIMD_AVX512 bit. The lane buffers for every worker are one
  block made at create. As elsewhere, the tiers are built per function
  and, under PlayStation clang or clang-cl, only where the baseline
  enables them.

Per frame at 256x224 on one thread, AVX-512 Xeon:

  original           10.0 ms
  previous commit     7.5 ms
  AVX2                3.85 ms
  AVX-512             3.67 ms

(AVX-512 adds less than its width: the passes are bound by how many
operations issue, and 512-bit ones have two ports to AVX2's three.)

samples/gfx/crt_filter_golden now runs its table at every decode tier
the CPU has, with one and seven workers (seven leaves lines over at
both lane counts). Its matrix is smaller, since every CRT frame costs
about the same whatever its size: the input formats alternate between
scenarios. The table is recorded again from the original
implementation, from before the previous commit. 6 s plain, 18 s
under ASan/UBSan with all three tiers.

samples/gfx/filter_plugins creates the filters with the SIMD mask
RetroArch would pass on the runner, so its concurrent 1-against-2-3-4
workers check, and its ThreadSanitizer and ASan runs, cover a filter's
wide paths too.
2026-10-28 06:17:37 +01:00
Codex 589f2b688b Fix Switch overlay linking and park horizon regression test
Mock CPU capabilities alongside the Switch harness frontend stubs. Check three horizon-driven handler passes within the lane deadline, with the scheduler timing allowance, so sanitizer scheduling delays do not fail a fixed-rate assertion.
2026-10-28 06:16:09 +01:00
Codex ebeac8ea66 Fix standalone sample CPU feature dependencies
Link the sinc dispatch harness with the string compatibility helper required by ARM CPU detection. Include scalar and NEON output comparisons so ARM runs exercise the tap-shape check.

Supply scalar CPU capability mocks in the isolated GL and recording harnesses alongside their existing frontend mocks.
2026-10-28 05:44:13 +01:00
LibretroAdmin e3667367b9 video_filters/ntsc_crt: keep filter state and settings out of memory
At 256x224 the NTSC-CRT filter took about 7 ms a frame on one thread:
1.7 ms modulating the composite signal and 0.4 ms starting the decode,
both on the calling thread before the workers, then 5 ms decoding
lines on the workers.

Most of that was the compiler fetching and storing things it could
have held. The signal is signed char and the decoded output is stored
through int and unsigned char pointers, any of which may alias the
filters' state and the settings, so every store forced them back to
memory:

- The decoder runs the three-band equaliser for Y, I and Q on every
  sample through struct EQF pointers. EQF_STEP now runs it on locals,
  the same updates in the same order. The original eqf() is left for
  the configurations that still call it.
- The decoder's pixel loop read contrast, blend and the output format
  from the CRT struct for every pixel; they are read once a call.
- The modulator divided to find each sample's source pixel, switched
  on the pixel format, ran its three IIR filters through globals and
  recomputed the white level, which divides by 100, for every sample.
  For RGB input, the format the wrapper feeds it, the source columns
  are a table built once a frame, the filter history is in locals for
  the line (it is reset at every line), and the levels are hoisted.
  Other formats keep the general loop.

Everything is integer arithmetic and the operations are unchanged, so
the output is the same byte for byte. Per frame at 256x224, one
thread:

  modulate       1.7-1.8 -> 1.0-1.1 ms  (serial, before the workers)
  decode lines   5.1-5.6 -> 3.7-4.2 ms
  total          7.1-7.9 -> 5.1-5.9 ms

samples/gfx/crt_filter_golden holds the filter to output hashes
recorded from the previous implementation: noise off, default and
heavy, scanlines, blend, monochrome, hue, contrast, brightness,
saturation, black and white point, both input formats, several sizes
including frames smaller than the created maximum, one to four
workers and four frames in a row (field, frame parity and the noise
generator carry across frames); it runs plain and under ASan/UBSan
in the filter plugins workflow, beside the NTSC and 2xBR ones.
2026-10-28 05:43:45 +01:00
LibretroAdmin cdf84534e2 cheat_manager: search by region, skip ruled-out items, one loop per test
A memory search visited every item of the core's memory in turn: a
region lookup, a branch on item size, a loop over sub-items that at
byte size and wider runs once, and a branch on the search type, even
for items an earlier search had already ruled out - after a search or
two, nearly all of them. Each failing item also cost a memset call,
and the match count was reloaded and stored every item, since stores
through the uint8_t match bytes may alias it.

Now:

- Regions are walked in order, items at multiples of their size lying
  whole inside one, as before, without a lookup per item.
- Runs of clear match bytes are stepped over 16 bytes at a time with
  SSE2, 32 with AVX2 or 64 with AVX-512BW (chosen at run time from
  cpu_features_get(); built per function, and under PlayStation clang
  or clang-cl only where the baseline enables them), or a word at a
  time elsewhere. An item is still judged by its first match byte: a
  set byte part-way through one, which a change of item size between
  searches can leave, rounds back to the item's start.
- At byte, 16- and 32-bit sizes the search type is chosen once and
  each type gets its own loop, with direct stores for a failing item
  and the count held in a local. The 1-, 2- and 4-bit sizes keep the
  general path.

32-bit values were assembled as byte * 256 * 256 * 256 in int, which
overflows (undefined) for a top byte of 0x80 or more; they use
unsigned shifts now, the same value.

33 MB of memory on an AVX-512 Xeon, before -> after:

  8-bit items:  first search 270-344 -> 45-48 ms,
                after one search 146-188 -> 91-126 ms,
                after two 128-162 -> 9-12 ms
  32-bit items: first search 75-125 -> 18-80 ms,
                later searches 49-66 -> 5-11 ms

samples/cheats/cheat_search keeps the previous algorithm as its
reference and checks the match bytes, the match count and the saved
memory after each of 14400 chained searches: random region layouts,
every item size, both byte orders, every search type, memory changed
between searches in runs or scattered, the item size changed without
starting over, and the scan capped at each SIMD tier in turn.
samples/cheats/cheat_regions reports no SIMD, so it runs the baseline
scan.
2026-10-28 05:43:40 +01:00
LibretroAdmin 09a110512b zip: a bzip2 member not mapped is read through a ring
A bzip2 member decoded to a file and neither mapped nor lent whole is
read into a ring of twice the most a block can take, kept that far
ahead of the decoder, instead of being read whole.
2026-10-28 05:43:36 +01:00
LibretroAdmin c9ffbdb3cf rbzip2: input in two pieces, and the bit buffer filled a word at a time
A stream can be handed over in two pieces, as a ring buffer wraps, and
again before each block from where rbzip2_taken() says it was taken
to. The bit reader moves onto the second piece in the slow path it
takes at the end of the input.

The bit buffer is filled when under 24 bits are left, with one load of
as many octets as it holds: 4-6% fewer instructions decoding.
2026-10-28 05:43:36 +01:00
LibretroAdmin 68cc756926 zstd: frames given a block at a time, and the window moved once per window
rzstd_blocks_open(), _want() and _give() decode a frame handed over
in the pieces it asks for: the next block and the next block's header,
each decoded where it was read. A .zst file and a zip member stored
with Zstandard that are neither mapped nor lent whole are read a block
at a time into a buffer of one, rather than whole into memory.

Past a single segment the buffer a frame is decoded through holds
twice the window, so the window is moved down once per window's worth
decoded rather than once a block.

rzstd_raw_block_test decodes its frames in pieces too, and refuses one
a piece short; archive_zst_test and archive_zip_methods_test extract
their windowed frames read a block at a time where nothing is mapped.
2026-10-28 05:43:36 +01:00
LibretroAdmin 49a933f237 zip: a stored member bound for a file is written from where it lies
A stored member extracted to a file goes out from the mapping, or what
the stream lends, however small, without being copied into a buffer
first. A member read to a file for a core that wants a path is written
out as it is decoded, as an extracted one is.

archive_zip_test reads every member of its sequence to a file, and with
the CRCs wrong gets none.
2026-10-28 05:43:36 +01:00
LibretroAdmin 13a9965063 7z: a member that is its folder is handed over when read whole
Every buffer a folder's output may end up in has an octet to spare
after it, so a member read whole that is its folder's entire output,
every member of a non-solid archive, is the folder's buffer itself,
handed over with a NUL after it rather than copied. Other members are
copied out of the folder once, as before.

r7z_archive_detach_test writes the spare octet of everything it is
handed.
2026-10-28 05:43:36 +01:00
LibretroAdmin ddda8bef12 rar: a streamed member's buffer moves once per window, and a member read whole is handed over
The buffer a member streams through reaches as far past the window
again as the window, up to 64 MiB, and moves down once that much more
is final, so each octet is moved about once, and at every dictionary
size the point it moves at is one it reaches.

rrar_archive_extract() leaves an octet to spare after the member, and
the RAR backend hands that buffer to the caller with a NUL in it rather
than copying it.

rrar_archive_test streams with the buffer reaching 32 KiB past the
window, so it moves many times a member, and writes the spare octet of
every member it extracts.
2026-10-28 05:43:36 +01:00
LibretroAdmin 51394c15c4 rzstd: a frame's checksum is checked
A frame that carries one has its XXH64 taken over each block as it is
decoded, where the block lies, and checked against the low four octets
at its end, whole or a block at a time. A frame without one costs
nothing more. Everything that builds encoding_rzstd.c builds
encoding_xxh64.c beside it.

archive_zst_test reads and extracts a frame from libzstd whose checksum
is a bit out, and gets nothing either way.
2026-10-28 05:43:36 +01:00
LibretroAdmin 7777c03aed encodings: XXH64 and XXH3
xxh64() and xxh64_reset()/update()/digest() give XXH64, and xxh3_64()
the 64-bit XXH3, seeded or not, as xxHash specifies them. Past 240
octets XXH3 runs on AVX-512, AVX2 or SSE2, whichever the CPU has, or
NEON, or scalar code.

XXH3 adds each stripe's octets to a sum of their own and swaps that sum
into the accumulators once a block, so a stripe is five operations and
each accumulator one add long. A seed does not make a secret of its own
on AVX-512: every key is made in a register once per input. Each stripe
asks for the data eight and sixty-four stripes ahead.

xxhash_test checks both against xxHash's own values, XXH64 in pieces of
every size, and each XXH3 path the CPU runs against the others at every
length up to three blocks and every alignment.
2026-10-28 05:43:35 +01:00
LibretroAdmin 8f1ac4ad61 samples: compat_strl.c beside features_cpu.c
features_cpu.c copies the CPU's model name with strlcpy() on the
architectures that read it from /proc/cpuinfo, so every sample that
links it links compat_strl.c too; fast_cores_test includes it beside
features_cpu.c. They link on PowerPC and ARM as well as x86.
2026-10-28 05:43:35 +01:00
github-actions 2edc4a99fb Fetch translations from Crowdin 2026-10-28 00:18:29 +00:00
LibretroAdmin 2ff6dae5e4 video_filters/2xbr: per-worker padded keys, flat-pixel fast path
Each frame the packet setup filled a shared buffer with one YUV key
per source pixel on the calling thread before the workers started,
and every pixel then clamped all of its 25 key lookups and 9 pixel
loads to the frame edge and loaded the whole 5x5 neighbourhood, even
where none of its four corner rules could fire.

Now:

- Each worker fills its own key rows: its slice of the frame plus the
  two rows either side, padded by two keys at both ends, with rows
  past the frame and the padding repeating the nearest edge key. That
  is the clamp the neighbourhood reads through, so the key lookups
  need none, and the key pass runs on the workers in parallel instead
  of serially beforehand. The buffers are sized at create from the
  largest frame and thread count; a frame beyond that size (which
  the softfilter contract rules out) is pixel-doubled as before
  rather than growing a buffer mid-frame.

- A corner rule needs the centre pixel to differ from both pixels
  beside that corner. When no corner can fire, the four output pixels
  are the centre pixel and are written without loading the diagonal
  pixels or any key. Flat areas, the bulk of a game frame, take this
  path.

The corner rules and blends themselves are untouched, so the output
is unchanged byte for byte. Per frame at 256x224 on one thread:

  game-like frame  RGB565    870 ->  546 us
                   XRGB8888  844 ->  373 us
  noise            RGB565   4260 -> 3620 us
                   XRGB8888 3090 -> 2414 us

samples/gfx/xbr_filter_golden holds the filter to output hashes
recorded from the previous implementation: game-like frames and
noise, both formats, sizes from 1x1 to 320x240, frames smaller than
the created maximum, input rows at a wider stride, and one to four
workers; it runs plain and under ASan/UBSan in the filter plugins
workflow, beside the NTSC one.
2026-10-27 20:54:19 +01:00
LibretroAdmin 8240804eac video_filters/ntsc: sliding-window decode, about five times faster
The NTSC-Multi-System filter decoded each output sample by summing 12
(16 for Atari, 8 for C64) products of the composite line and the
carrier, taking an integer modulo for the carrier phase and clamping
the position for every product, then dividing twice by a run-time
value. At 256x224 that cost about 5 ms a frame on one thread.

Each product depends only on the input position, not on which output
sample reads it, so it is now computed once per position into a line
padded with the edge sample (the clamp the window and the notch read
through), and each output takes a running window sum: two adds and
two subtracts in place of the products, modulos and clamps. The
carrier phase advances by a step and wraps with a compare instead of
a modulo, in the encode as well. The window pass is instantiated per
system so the divisor is a constant the compiler can turn into a
multiply or shift; C's truncating division is kept.

Everything is integer arithmetic, so the output is unchanged byte for
byte: compared against the previous code in every system mode, with
sharpness, hue/saturation and both artifact extremes, both formats,
widths 1 to past the 1024 cap, one and three workers and consecutive
frames (1560 cases).

  256x224 RGB565:   4.8-5.2 ms -> 1.0 ms per frame
  256x224 XRGB8888: 4.8-5.0 ms -> 1.0 ms per frame

The YIQ cache the old encode filled and never read, and the separate
composite buffer, are gone from the per-worker scratch.

The sharpness pass shifted a negative value left (Y << 1), which is
undefined; it is Y * 2 now, the same value on every target.

samples/gfx/ntsc_filter_golden holds the filter to output hashes
recorded from the previous implementation over the same matrix, plain
and under ASan/UBSan, in the filter plugins workflow.
2026-10-27 20:49:15 +01:00
LibretroAdmin b9c0d2796b netplay: run on from a resync state that arrived past the next frame
A client behind a lockstep host can be handed a state labeled with a
frame it has not reached yet, and when that state is read after the
frame has advanced the forced rewind loads it one frame past run. The
rewind now makes the state's frame the one run next, so the client
never runs an earlier frame's input on top of the host's state and
the resync actually mends the desync.

samples/network/netplay_e2e gains a lane where the client runs slower
than the host and has its RAM upset, and NETPLAY_E2E_PACE_MS to set a
peer's pace.
2026-10-27 20:47:59 +01:00
LibretroAdmin 06e53f5d54 s16_to_float: build only baseline ISA tiers under PlayStation clang
As the other run-time SIMD units: Clang's immintrin.h for PlayStation
targets and clang-cl declares only the intrinsics the whole build
enables, so the AVX2 and AVX-512 conversions are built there only when
the baseline carries them.
2026-10-27 20:47:56 +01:00
LibretroAdmin cabaa7b04d x86 SIMD: build only baseline ISA tiers under PlayStation clang
Clang's immintrin.h for PlayStation targets (and clang-cl) declares
only the intrinsics of ISAs the whole build enables, so the AVX2 and
AVX-512 kernels that sinc_resampler, pixconv, float_to_s16 and
state_manager compile under per-function targets do not exist there.
On those compilers a tier is built only when the baseline carries it:
the PS4 keeps sinc's AVX kernel, the PS5 keeps the AVX2 tiers, and the
run-time choice never picks a tier that was left out.
2026-10-27 20:39:56 +01:00
LibretroAdmin 7b23ab8aa4 s16_to_float: run-time AVX2 and AVX-512 conversion
convert_s16_to_float widens every s16 core's audio before the
resampler, on every flush. It gains AVX2 and AVX-512 forms of the SSE2
loop, picked by convert_s16_to_float_init_simd() from
cpu_features_get(), built per function so older CPUs keep SSE2 and
compilers without per-function targets (before GCC 5, Clang 4, MSVC
2017 15.3) leave them out.

Each sample is an exact sign-extend and convert, then one multiply by
gain / 32768: the same single rounding as the SSE2 and scalar paths,
so every target gives the same float for the same sample. AVX2 takes
16 samples a step; AVX-512 masks its last step, so it has no scalar
tail.

One 534-frame stereo batch (1068 samples) on an AVX-512 Xeon:
SSE2 0.14 us, AVX2 0.07-0.09 us, AVX-512 0.08-0.10 us.

New libretro-common/samples/audio/s16_to_float pins each tier the CPU
has and compares it bit for bit with the scalar rule: every s16 value
at six gains, then every length up to 100 at odd offsets, with a guard
word after each buffer.
2026-10-27 20:39:36 +01:00
LibretroAdmin 5811daa1e5 libretro-common: let MSVC builds reach the SSE2 paths of five SIMD files
pixconv.c, scaler_int.c, float_to_s16.c, float_to_s32.c and
s16_to_float.c test __SSE2__, which GCC and Clang define but MSVC does
not. retro_environment.h translates MSVC's _M_X64 / _M_IX86_FP into
__SSE__ / __SSE2__, but none of the five included it, so outside a
unity build that happens to pull it in first (griffin), an MSVC build
of any of them - a core built for UWP or Xbox, say - compiled the
scalar paths only.

Include it at the top of each. Preprocessed with MSVC's macros in
place of GCC's, all five now take their SSE2 paths. Those paths use
only SSE2 intrinsics MSVC has had since 2010; the 64-bit-only
_mm_cvtsi128_si64 in scaler_int.c is behind __x86_64__, so MSVC x64
takes the portable branch beside it. pixconv.c keeps its existing
SCALER_NO_SIMD for MSVC 2013 and older. GCC and Clang builds are
unchanged.
2026-10-27 20:32:29 +01:00
LibretroAdmin f09df05e0f float_to_s16: run-time AVX2 and AVX-512 conversion
convert_float_to_s16 narrows the resampled mix for every s16 audio
driver on every flush. It gains AVX2 and AVX-512F forms of the SSE2
loop, picked by convert_float_to_s16_init_simd() from
cpu_features_get(), built per function so older CPUs keep SSE2 and
compilers without per-function targets (before GCC 5, Clang 4, MSVC
2017 15.3) leave them out.

Both run the SSE2 arithmetic step for step - scale, NaN to zero,
copysign(0.5) bias, clamp, truncate - so every target still gives the
same s16 for the same float. AVX2 converts 16 samples a step and fixes
the per-lane pack order with one permute. AVX-512 masks its loads and
uses the masked saturating narrow-and-store for the last step, so it
needs no scalar tail.

One 800-frame stereo flush (1600 samples) on an AVX-512 Xeon:
SSE2 0.45 us, AVX2 0.24 us, AVX-512 0.22 us.

New libretro-common/samples/audio/float_to_s16 pins each tier the CPU
has in turn and checks it against the scalar rule on exact .5 points,
full scale and past it, infinities, NaNs of both signs, denormals and
random bit patterns, at every length up to 100 and at odd offsets,
with a guard word after each buffer.
2026-10-27 20:32:25 +01:00
LibretroAdmin 5579481562 pixconv: run-time AVX2 and AVX-512BW 16-bit to 32-bit converters
conv_rgb565_argb8888, conv_rgb565_abgr8888 and conv_0rgb1555_argb8888
are what the SDL2, SDL3, GL1 and DXGI upload fallbacks and the scaler
run every frame for 16-bit cores. They gain AVX2 and AVX-512BW forms
of the SSE2 arithmetic: 16 and 32 pixels a step, with a permute to put
the per-lane unpacks back in pixel order. The AVX-512 form also takes
each row's tail through masked loads and stores instead of a scalar
loop.

They are built per function and picked once at run time from
cpu_features_get(), so older CPUs keep the SSE2 path and compilers
without per-function targets (before GCC 5, Clang 4, MSVC 2017 15.3)
leave them out. Only RetroArch's own build (RARCH_INTERNAL) or one
defining PIXCONV_RUNTIME_SIMD compiles them, so cores that carry
pixconv.c without features_cpu.c build as before. The cached tier is
published with a release store, as converters run on several threads.

Microseconds per frame, RGB565 -> ARGB8888, on an AVX-512 Xeon:

  size        sse2   avx2  avx512
  256x224     14.2    8.2     8.3   -42%
  320x240     18.8   10.4    10.4   -45%
  640x480     75.7   44.1    41.3   -45%
  1280x720   221.0  195.2   198.3   -10% (bound by memory writes)

The ABGR and 1555 forms gain 28-42% at the same small sizes.

pixconv_test_wide builds the converters in with a test hook that caps
the tier, runs every 16-bit input through each tier the CPU has, and
every row width from 1 to 100 with a guard word after each row, so
every tail length is checked for writing past the row.
2026-10-27 20:21:55 +01:00
LibretroAdmin f7c9d23570 sinc_resampler: x86 kernels read short filters straight from the input
Every input frame was split into the planar L/R rings, each sample
stored twice so a window stays contiguous: four scalar stores per
frame, about a quarter of the resampler's time at Lowest to Normal.

New SSE, AVX and AVX-512 kernels read the window straight out of
data_in, interleaved as the core gave it. Only windows reaching back
past the start of a batch read from a seam: the last 'taps' frames of
history plus the first 'taps - 1' of the batch, copied once per batch.
The seam fits in the 4 * taps floats the rings use, so the shadow
copy, reset and stage restart work unchanged.

The window is read oldest first, so for these kernels the phase
tables are reversed at init and laid out in blocks where one unpack
of a block with itself gives the coefficients doubled up per L/R
pair, in frame order, with no cross-lane shuffle. A trailing block of
8 taps under AVX-512 is doubled with one permute instead.

That unpack competes with the multiply-adds for a port on long
filters, so the ring kernels stay for those. Each instance picks at
init by tier and tap count; the crossovers were measured: SSE up to 16
taps, AVX up to 128, AVX-512 up to 192. C and NEON are unchanged.

ns per output frame at 32040 -> 48000 Hz, 534-frame batches, paired
against master on an AVX-512 Xeon:

  quality  tier    taps  before  after
  lowest   sse        8    5.89   3.83   -35%
  normal   sse       16    8.29   7.00   -15%   (the default)
  higher   avx       64   19.10  14.59   -23%
  higher   avx512    64   19.45  14.72   -28% to -50%
  highest  any      256   unchanged (ring kernels)

sinc_dispatch now checks each SIMD tier separately against the scalar
kernel, both with one batch and with a stream of uneven batches (some
shorter than the filter), so history is carried across the seam. With
the seam's carry-over disabled the test fails, as it should.
2026-10-27 20:13:18 +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 f46db3577e state_manager: AVX-512BW rewind scans, line-aligned blocks, per-tier codec
Rewind diffing (find_change for the next dirty word, find_same for the
end of a dirty run) gains an AVX-512BW tier next to SSE2 and AVX2, and
find_same gets the AVX2 form it lacked. Tiers are picked once from
cpu_features_get(); RETRO_SIMD_AVX512 promises BW.

Three things make the wide scans pay off rather than lose to SSE2:

- The two blocks are now 64-byte aligned and a whole number of lines
  apart, so a scan position has the same offset within a line in both.
  The wide loops step back to a line boundary inside bytes already
  proven equal, and from there no load straddles a cache line on
  either block. Before this, AVX-512 lost to SSE2 on dirty states.

- Each scan starts narrow (16 bytes, then 32) and only then goes to
  full width, since most scans in a dirty state end within the first
  few words.

- raw_compress is compiled once per tier with the scans inlined. A
  target function cannot inline into baseline code, and a call per
  scan cost more than the scan on dirty states.

All tiers write byte-identical patches. Median time against SSE2,
paired runs on an Emerald Rapids Xeon (lower is faster):

  state  pattern          avx2  avx512
  16K    clean            0.49   0.28
  64K    1 dirty in 4096  0.60   0.45
  256K   16-word runs     0.61   0.49
  256K   1 dirty in 256   0.70   0.61
  1M     clean            0.85   0.84
  any    1 dirty in 64    ~1.0   ~1.0-1.05
  4M+    any              ~1.0   ~1.0   (memory bound)

samples/state_rewind allocates its blocks the same way and checks that
every tier the CPU has writes the same patch over 300 seeded pairs,
with the sentinel and scan-pad cases re-run under each tier.
2026-10-27 19:49:10 +01:00
LibretroAdmin f7a8815975 sinc_resampler: runtime-dispatched AVX and new AVX-512 kernels
The AVX kernels were only built when the whole build had __AVX__, and
no RetroArch build passes -mavx, so shipped x86 builds never ran them
and Higher/Highest/HQ always used SSE.

Build the kernels whenever the compiler can target a single function
(GCC 4.9+, Clang 4+, MSVC 2010+ for AVX and 2017 15.3+ for AVX-512)
and select them at init from the mask cpu_features_get() fills in.
The rest of the file stays at the build's baseline, so CPUs without
AVX never execute an AVX instruction, and older compilers leave the
kernels out and keep the SSE/C paths. MSVC clears the upper vector
halves on return explicitly; GCC and Clang do it for target functions.

Add AVX-512 kernels: 16 taps per step with fused multiply-add, a
masked load for a leftover block of 8 taps instead of a second loop,
and both channels reduced and stored as one 64-bit write. They take
over where AVX is used today (Higher, Highest, HQ); Lowest, Lower and
Normal stay on SSE, which is still faster at their tap counts.

Measured on a 2.1 GHz Xeon with AVX-512, ns per stereo output frame:

  quality   taps    sse    avx  avx512
  higher      64   26.6   23.1    18.2
  highest    256   77.3   60.3    41.8
  hq         384  147.1  107.4    86.6

sinc_dispatch now runs every kernel the CPU supports against the
scalar kernel at three ratios and two bandwidths, one of which gives
a tap count that exercises the masked tail.
2026-10-27 19:32:54 +01:00
LibretroAdmin 064450ef90 features_cpu: gate AVX2/AVX-512 on OS register state, define AVX-512 as x86-64-v4
AVX2 was reported from the CPUID bit alone. AVX2 runs on YMM
registers, so on an OS or VM that does not save YMM state (no
OSXSAVE, or XCR0 without the AVX bit) code dispatched on
RETRO_SIMD_AVX2 would fault. Require the same ymm_state check AVX
and FMA3 already use.

The AVX-512 check called xgetbv without first proving OSXSAVE is
set. xgetbv is an invalid instruction until the OS enables XSAVE, so
a CPU with AVX-512F under such an OS crashed during feature
detection. Test ymm_state first.

RETRO_SIMD_AVX512 now means the x86-64-v4 baseline: F, CD, BW, DQ
and VL. Code dispatched on it can use byte/word ops and 128/256-bit
masked forms, which is what pixel conversion, rewind diffing and
audio work need; F alone is useless for those. Every AVX-512 CPU
except Xeon Phi has all five. The Darwin path checks the matching
hw.optional keys, since macOS enables ZMM state lazily and xgetbv
cannot decide it there.
2026-10-27 19:24:45 +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 cf60e94e40 scan: RAR archives and .bz2 files matched through their members
The scanner treats .rar as an archive, and .bz2 is a compressed file
the way .zst is, so a .bz2 is opened even when no core lists it; ".7Z"
is an archive as ".7z" is.

database_scan_test scans a 7z, a RAR, a RAR 5 with BLAKE2, a .zst, a
.bz2 and a zip with a Zstandard member, each matched through its
member, on both queues.
2026-10-27 18:29:38 +01:00
LibretroAdmin 9e40aaa8c1 archive: a member's CRC computed where the archive records none
file_archive_get_file_crc32() asks for a member's CRC with crc_wanted.
The .zst backend decodes the frame a block at a time for it, and the
RAR backend unpacks a RAR 5 member that carries a BLAKE2 hash in place
of a CRC.

archive_zst_test and archive_rar_test check the CRC of a .zst member
and of a RAR 5 member with BLAKE2.
2026-10-27 18:29:38 +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
LibretroAdmin efe2158a48 rsx: native RSX context, follow-ups
Give the RSX context and render interface proper IDs in libretro.h
(RETRO_HW_CONTEXT_RSX = 11, RETRO_HW_RENDER_INTERFACE_RSX = 6) and ship
one interface, version 1, with retire_memory in it, instead of two
versions picked through the context's version_major.

Retired memory goes on a ring and is covered by one label write per
frame, with no wait-for-idle and no flush per call.

Font vertex pages are all allocated with the font, and batches bigger
than a page are drawn in page-sized pieces, so nothing is allocated
while drawing.

When the menu shows over a duped frame from a hardware core, the core's
image is drawn behind it rather than the software texture. The frontend
state reset runs only on frames the core rendered.

The host mapping default stays 32 MB; RSX_HOST_SIZE still overrides it.
The PSL1GHT guards around the new code are dropped: the driver only
builds with HAVE_GCM, which only Makefile.psl1ght sets.
2026-10-27 17:52:21 +01:00
jcorrea 9ec7de02d3 feat(ps3): add native libretro RSX hardware rendering support
Expose an experimental libretro RSX context and versioned rendering
interface for PSL1GHT cores. Present core-owned RSX textures without CPU
uploads, share the command FIFO and unused host mapping, and defer
allocation retirement until queued GPU work completes.

Integrate context negotiation and enforce main-thread rendering. Restore
frontend GPU state before compositing and use fenced per-font vertex
pages to prevent oversized Ozone batches from corrupting queued text.
Invalidate the vertex cache before menu and font draws.

Make the RSX host allocation configurable at build time. Extend regression
coverage for hardware image validation, deferred resource retirement,
oversized font batches and allocation failures.
2026-10-27 17:52:21 +01:00
libretroadmin b0c6631384 Cleanup 2026-10-27 17:47:42 +01:00
Codex dca189a779 ci: partition libretro-common samples within the job budget
Run codecs, other formats and core samples in separate jobs, retaining every directory and assigning follow-up checks to one group. Bound parallel builds and keep per-group compiler caches.

Register the MP4 VP9 hardware-stream test for execution rather than leaving its built target unclassified.
2026-10-27 17:44:32 +01:00
Codex cacc932f47 ci: keep D3D12 upscaler checks on one Wine server
Use a private prefix and the existing persistent-server helper inside one X display. Exercise both cold and warm test runs, preserving their exit status and bounding the complete Wine invocation.
2026-10-27 17:37:26 +01:00
Codex 6ca16da741 menu: query current input data through the frontend
Keep the device-ignore callback out of the input state layout while passing the same driver data to the existing joypad restart path.
2026-10-27 17:33:52 +01:00
LibretroAdmin 9bc350d5b8 zstd: frames decoded a block at a time to the output file
rzstd_blocks_begin()/step()/free() decode a frame through a buffer of
its window and two blocks, which slides down keeping the window. The
frame stays whole in memory, as with bzip2.

Zip members stored with Zstandard and .zst files bigger than 256 KiB
are written out a block at a time. archive_zip_methods_test and
archive_zst_test extract a frame from libzstd with a 128 KiB window
whose matches reach nearly that far back.
2026-10-27 17:06:03 +01:00
LibretroAdmin 8b8a23d437 rzstd: four-stream literals in memory order on big-endian hosts
The five symbols a stream gives per round are gathered into a word in
the order they go into memory, whichever the host's byte order.
archive_zst_test, run on a big-endian host, extracts frames whose
literals are in four streams.
2026-10-27 17:06:03 +01:00
LibretroAdmin 94d9d2813e rar: members streamed to the output file
rrar_archive_extract_sink() hands a member out in pieces: stored ones
straight from the archive, compressed ones from a buffer of the window
and room for a filter block, which slides down as output goes. A
filter block goes out filtered once it is all there. The CRC is checked
at the end.

The RAR backend extracts members bigger than 256 KiB this way.
rrar_archive_test streams every fixture through a small step and
compares it with whole extraction; archive_rar_test extracts the big
member and leaves no file when a byte of it is changed.
2026-10-27 17:06:03 +01:00
LibretroAdmin dfe190e966 7z: LZMA and LZMA2 members streamed to the output file
A member bigger than 256 KiB of a folder with one LZMA or LZMA2 coder
over one pack stream is decoded through a ring of the dictionary's
size, at most 256 KiB a step, and written out as it goes. Its CRC, or
the folder's when the member is all of it, is checked at the end.
Other folders are decoded whole.

archive_7z_test extracts LZMA, LZMA2 and Deflate members bigger than
that, and refuses one with a byte of its stream changed.
2026-10-27 17:06:03 +01:00
LibretroAdmin e51ea57c3f r7z_lzma_stream: a dictionary reset keeps where the window is
An LZMA2 dictionary reset, which 7-Zip writes at every block when it
compresses on several threads, forgets what came before without moving
where output goes. Literal and position contexts count from the reset,
and the byte before the window's start is its end.

r7z_methods_test extracts a two-block LZMA2 archive whole and in
slices, and decodes LZMA through a ring smaller than the stream.
2026-10-27 17:06:03 +01:00
LibretroAdmin c430e3e927 android: an overlay press buzzes when pressed, not a frame later
The keypress haptic was held to the top of the next runloop iteration,
up to a frame after the press: the press can come on a core's libco
stack, where Java is not to be entered. A thread of its own - the shared
output writer, attached to the JVM by rjni as it first asks - now makes
the buzz the moment it is woken; waking it is no Java. The pending flag
is an atomic, cleared by the lifecycle handlers as before, and the
runloop still flushes it where there are no threads.
2026-10-27 17:05:28 +01:00
LibretroAdmin ea6776fb04 overlay: send a stick's geometry when it moves, and copy less per poll
A movable desc's geometry went to the video driver every poll, moved or
not. On D3D11 each call marks the overlay's vertex buffer dirty, so it
was mapped and rewritten every frame; under threaded video each was a
packet through the ring. The last delta sent is kept per desc and the
geometry is sent only when it differs - 23 times across an 8 second
on-screen run where it was about 500 - and reset wherever the whole
page's geometry is set.

The poll copied the last state and cleared the new one, about 150 bytes
each, every poll: the overlay now keeps both and flips between them
(INPUT_OVERLAY_ST()), clearing only the new one's keys, sticks and
buttons. The state each touch is tested into is cleared only there too,
its touches being unused.

Each finger was read with three calls into the pointer frame; the frame
store answers down-or-not and where in one look
(input_pointer_frame_finger), with the driver asked as before when the
frontend does not hold the pointer.
2026-10-27 17:05:24 +01:00
LibretroAdmin 226cec7da5 overlay: a movable stick follows the finger with Show Inputs off
With "Show Inputs" off, the overlay took each control's state from the
physical controller, as for "Physical": a movable stick's image was set
to the controller's stick - at rest - over where the finger had put it,
every poll, and never moved. Only "Physical" reads the controller now;
off reads the touches as "Touched" does, and shows nothing.

overlay_stick_on_screen_test runs the real binary under Xvfb with a
movable stick, drags it right and up, holds it and lets go, and looks
at where it is drawn each time, threaded video off and on.
2026-10-27 17:05:20 +01:00
libretro e8928377d1 build: keep the macOS frontend compatible with C89 2026-10-27 16:10:02 +01:00
Codex fe3697ba94 media: add VideoToolbox VP9 and MP4 hardware playback
Keep VP9 decoding and native frame leases on the existing asynchronous Metal owner path. Add optional decoder restart and MP4 hardware submission with bounded backpressure, terminal drain and retained-frame ownership.

Cover configuration, lifecycle, superseded renderer ownership, seek restart and EOF with portable contract tests. Add native Metal pixel/pipeline acceptance and a Vulkan MP4 reference oracle.
2026-10-27 16:10:02 +01:00
LibretroAdmin e0b5626af6 zip: Deflate64, LZMA and bzip2 members streamed to the file
A member of these bound for a file goes to it a window at a time, as
stored and deflated ones do, rather than whole through memory.
rinflate64_process() decodes Deflate64 in pieces from input in parts,
keeping the last 64 KiB for matches that reach back past its output;
rinflate64_decode() is one call of it, and takes eight octets in a
load where it can, which makes it a fifth faster. An LZMA member
decodes through r7z_lzma_stream into a window of its dictionary's
size, written out from where it lies. rbzip2_begin_blocks() and
rbzip2_step_block() hand a stream's blocks over one at a time from the
decoder's own buffer; a bzip2 member's stream is still read whole.

archive_zip_methods_test extracts members of each bigger than the
window, the LZMA one's dictionary wrapping, and leaves no file for a
wrong CRC or a changed byte; r7z_methods_test decodes Deflate64 in
pieces through windows of 1 to 70000 octets, and a match to the first
octet and one before it; rbzip2_test takes a stream's blocks one at a
time.
2026-10-27 15:20:21 +01:00
LibretroAdmin 71ce6182c4 database_scan_test: the unclaimed 3DS check holds on a second run
The playlist it checks is not written is removed before that scan and
after the block, so a root an earlier run left it in does not fail
the check. make check runs the scan test twice in one root to keep it
so, and gives the budget test its sanitizer allowance when SANITIZER
is set, as CI does.
2026-10-27 15:20:21 +01:00
LibretroAdmin a21637debe input: ignore a controller from Input Information
Some duplicates nothing can tell apart: a remapper (DS4Windows,
reWASD, x360ce) reads a controller and shows a virtual one of it, and
nothing in the virtual pad's identity says so. Only the user knows,
so the user chooses.

Information > Input Information has a row under each controller,
"Ignore This Controller": that model is left out by every joypad
driver that knows a controller's ids - raw input, DirectInput, the
hybrid driver, udev and the HID drivers - the way the controllers
Steam Input stands in for already are. The controller driver restarts
so it goes at once. Each model left out is listed after the
controllers, a row to read it again. The list is the
input_ignore_devices setting, written as SDL's
SDL_GAMECONTROLLER_IGNORE_DEVICES is, and read beside that variable.

input_device_ignored() moves into input_driver.c, as it now reads the
settings; the check itself stays in input_ignore_devices.h with the
list's helpers, which ignore_devices_test covers. The driver tests
that link a driver are given the frontend's function.
2026-10-27 14:20:50 +01:00
LibretroAdmin 40583200c8 dinput: one port for a pad on cable and Bluetooth at once
A DualShock 4 or DualSense on Bluetooth and plugged in to charge is
two devices to DirectInput, and both the DirectInput driver and the
hybrid driver's DirectInput pads gave it two ports. Raw input already
told the links apart by the pad's Bluetooth address; DirectInput gives
the device's HID path too (DIPROP_GUIDANDPATH), so the enumeration
reads the address the same way and leaves a pad's second link out.

The address read moves to win_pad_ident.h, shared by raw input and
DirectInput. It finds HID's two calls in hid.dll, so a build that
links no HID library has it, and it runs where a pad connects - on the
enumeration's task for DirectInput - never on a frame.

win_pad_ident_test now holds a pad on both links to one port under
every Windows joypad driver. The raw input rumble test counted every
library load as XInput's; it counts XInput's.
2026-10-27 13:27:27 +01:00
libretroadmin 31d38e342f Revert "CI: keep artifacts consumed by other projects for a week"
This reverts commit 0e2d959749.
2026-10-27 13:23:09 +01:00
LibretroAdmin 0e2d959749 CI: keep artifacts consumed by other projects for a week
The static-core, Apple and Android jobs publish retroarch-precompiled/
and retroarch-repo/, which core pipelines and App Store packaging fetch
from the latest successful master job through cross-project needs.
Keep them for a week so they survive pipelines that end failed or
cancelled; the server-side size-based prune bounds the storage.
2026-10-27 13:13:08 +01:00
LibretroAdmin 0ff0ff5068 rbzip2: a block at a time; r7z: BZip2 folders a step at a time
rbzip2_begin() binds a stream and where it decodes to, and each
rbzip2_step() decodes a block of it, its CRC checked, until the end and
the stream's CRC; rbzip2_decode() is the same steps run through. The
7z BZip2 stage pauses on them like the others, with a decoder of its
own, and its folder fills from the front for r7z_archive_decode_peek().

rbzip2_test decodes a stream a block at a time, with a buffer one
short and with a block's CRC wrong; r7z_methods_test steps a folder of
three BZip2 blocks.
2026-10-27 13:10:02 +01:00
LibretroAdmin 5eec17e705 r7z: the ARM64 filter
7-Zip applies it to arm64 code: BL's word offset, and ADRP's page
offset within 512 MiB either way, made absolute, with the stream's
start from the coder's properties when it has them.

r7z_methods_test extracts 7-Zip's archive of an arm64 shared object,
undoes XZ Utils' filter of the same from 4096, and takes XZ Utils'
words that go back as well as forward and reach the ends of ADRP's
range and past them.
2026-10-27 13:10:02 +01:00
LibretroAdmin 432ce696fc archive: 64-bit member sizes, and ZIP64
Members' sizes reach the archive callbacks as 64 bits, so 7z, RAR and
.zst members of 4 GiB or more are listed and extracted where the host
can hold them. The zip backend reads ZIP64: the end of the directory
through its locator, and members' sizes and offsets from the 0x0001
extra field, the offset kept in the walk itself where a pointer cannot
hold it. A stored member bound for a file goes through the window a
piece at a time, as a deflated one does, so a zipped disc image is
extracted without the whole of it in memory.

archive_zip64_test reads a small archive with every ZIP64 field forced
and a sparse one past 4 GiB every way, and refuses a short extra field,
a missing locator and a record past the end.
2026-10-27 13:10:01 +01:00
Codex d44a83afda android: integrate asynchronous native GPU recording
Connect the MediaCodec recording frontend to worker-owned encoders and
Vulkan capture without frame-path readbacks or new waits. Keep capture
leases through native retirement and defer cold queue work on contention.

Calibrate AAC presentation edits, omit an empty audio source, and drain
healthy recordings before video resets. Retain the teardown path for
failed or ambiguous GPU work. Include portable lifecycle, audio, file
and capture regressions.
2026-10-27 13:02:24 +01:00
LibretroAdmin 1a7cd82f87 vfs: prefetch on the file's read-ahead thread, never the reader
On Windows a mapped file's prefetch hint is handed to the file's
read-ahead thread, which calls PrefetchVirtualMemory (or, before
Windows 8, touches the pages) while the caller returns at once.
PrefetchVirtualMemory is not documented to return before its reads
complete, and the reader is usually an emulation thread, so a cold
image must not stall it on the disk. Each hint starts past the read that
made it, and a range that goes on from the last continues where that one
got to, so no byte is requested twice.

vfs_prefetch_offthread_test stands a slow PrefetchVirtualMemory in
through VFS_WIN32_PREFETCH_HOOK and checks that no read waits on it,
that it never runs on the reader, and that the file is requested once.
2026-10-27 12:55:27 +01:00
LibretroAdmin 60d4261595 libretro-common: keep path scratch buffers off the stack
fill_pathname_abbreviated_or_relative, filestream_copy_loop,
retro_vfs_copy_begin_impl, retro_vfs_dirent_is_dir_stat,
retro_vfs_dirent_stat_slow and cpu_class_by_value take their
PATH_MAX_LENGTH and per-CPU scratch from the heap, so each frame stays
within the 2 KiB libretro-common thread-stack budget; the largest of
them is now 400 bytes on x86-64 GCC.
2026-10-27 12:55:27 +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
LibretroAdmin 9696bef97a udev: player lights through the pad's LED devices
The udev joypad driver lit nothing: it had no set_player, unlike the
other desktop drivers. It now finds the LED class devices under each
pad (its HID device, or an Xbox 360 pad's USB device) and lights them
as each console does: a DualSense's five player lights, a DualShock
4's light bar in the player's colour, a DualShock 3's four, a Switch
pad's four and an Xbox 360 pad's ring. A DualSense's light bar is left
alone, as are lights it never lit; ones it lit go out when no port
reads the pad. Four rings under one wireless receiver are not told
apart, and are left.

Writing a light can wait on the pad (a Switch pad answers a request),
so the rumble writer's thread writes them: the lights are opened where
a pad arrives and handed over through atomic slots, with the player
wanted an atomic of its own. Without threads the caller writes them,
as with rumble. A driver that adds a pad's lights just after its input
nodes is looked at again a few times.

The kernel makes these files root's.
pkg/linux/70-retroarch-player-lights.rules makes them writable by the
user; without it the pad is left as it is, and the log says so.

samples/input/udev_player_lights runs the real driver against a
directory laid out as /sys is, under ASan and TSan.
2026-10-27 12:36:07 +01:00
LibretroAdmin 561657acd9 tests: give the udev tests' pads no touchpad node
The DualShock 3 pressure and udev rumble tests zero a pad and set its
sensor descriptor to -1, but not the touchpad's, added since: it stayed
0, and every poll read stdin as the pad's touchpad. With stdin an open
pipe, as a CI step can have it, the read blocked until the 60 second
timeout.
2026-10-27 12:21:54 +01:00
LibretroAdmin 17d8ad9f86 input: one port for a pad Windows shows more than once
Which of Windows' views reads a controller is now decided in one place
(input/common/win_pad_ident.h) instead of four copies that disagreed.
The XInput-hybrid driver matched "IG_" in capitals only, so an Xbox pad
whose raw input path came lower case ("ig_") was read through XInput
and DirectInput both: two ports. The check is now case-insensitive
everywhere, and Bluetooth paths are told the same way, classic and LE.

A DualShock 4 or DualSense on Bluetooth and plugged in to charge is two
devices to Windows. Raw input now reads the pad's Bluetooth address
from each link at connect (the serial string over Bluetooth, a feature
report over USB, on a handle opened with no access): the second link of
a pad already there is kept aside and not read; when the first goes,
it carries on in the same port with no disconnect. DirectInput gives
no path to tell them by, and is left as it was.

samples/input/win_pad_ident holds a table of real controllers - Xbox
pads on USB, Bluetooth and Bluetooth LE with paths in either case,
8BitDo X and D modes, Sony pads on one or both links, Switch Pro,
WinUSB pads, Steam Input's - to one port each under every Windows
joypad driver.
2026-10-27 12:21:50 +01:00
LibretroAdmin d79eae767b Video: keep clock-grid pacing on the content's period under load
With vsync off and no present timestamps, the hold's "next vblank" is
only the video thread's last completion plus a period. Each frame's
due time was clamped to it, so whenever that thread ran late on a busy
machine the schedule slipped by the same amount and never caught up:
content ran 3-6% slow (57 fps for 60). The clamp now applies only to a
real display grid; the clock grid keeps exact content periods, as the
timestamp path already did.

The threaded video pacing-rate lane, which caught this as a "flaky"
failure on a loaded CI box, now measures from the gaps between frames
and leaves out gaps of two content periods or more - the machine
taking the CPU away, which no pacing can make up - and reports how
many. Every hold pattern a case has stays under that, so a wrong
period still fails. With both CPUs saturated, every case is now
within 1.1%.
2026-10-27 11:32:58 +01:00
LibretroAdmin e48ff3f2ad Input: add Hotkey Enable Tap Delay (#19871)
Where Hotkey Enable is also bound to a game button (Select, most
often), the game saw it for the first frames of every hotkey use.
With "Hotkey Enable Tap Delay" set, the game buttons sharing the
enabler's button or key are kept from the core until it is known
whether a hotkey follows:

- let go with no hotkey used, the press is given to the core then,
  for as many frames as it was held (two at least);
- held past the delay (real milliseconds) with no hotkey, it is the
  game's from then on, along with the other buttons;
- used with a hotkey, the core never sees it, or stops seeing it the
  moment the hotkey fires.

Off by default; when off nothing runs. The core's read tests it with
the core hold's existing branch. A frontend harness lane covers taps,
holds and hotkeys.
2026-10-27 11:32:55 +01:00
LibretroAdmin f9b6e25151 r7z: PPMd and Deflate folders decode a step at a time
r7z_archive_decode_step() pauses PPMd and Deflate stages the way it
pauses LZMA's, and r7z_archive_decode_peek() shows their folders
filling from the front. PPMd decodes a slice of symbols per step;
Deflate binds the folder's buffer once and rinflate_extend_out() moves
the end of the output on each step, so matches reach back into the
buffer itself and a step costs what the one-shot decode does.

r7z_methods_test steps 300 KB folders of each, reading them part way,
and refuses one with a byte changed.
2026-10-27 11:23:13 +01:00
LibretroAdmin a7e4c8391f archive_file_zlib: Deflate64 and LZMA members
Zip members stored with Deflate64 (method 9), as Windows and 7-Zip
write large files, decode whole through rinflate64_decode, and LZMA
members (method 14) through r7z_lzma when the 7z backend is built,
alongside the zstd and bzip2 modes: read whole, extracted, and through
an entry source.

archive_zip_methods_test reads 7-Zip's archives of both every way, with
Deflate64 matches over 32 KiB back, and refuses a wrong CRC and an LZMA
header without 5 octets of properties.
2026-10-27 11:23:13 +01:00
Codex 18b7d1867a deflate64: use overlap-safe bulk copies and retire stale exceptions
Keep periodic LZ matches on the bytewise path and use memmove for bulk matches. Remove obsolete copy-check exceptions.
2026-10-27 11:20:12 +01:00
Codex e2e41947cc settings: refresh the stylus overlay default reference 2026-10-27 11:20:12 +01:00
Codex 7ef96b4b50 Android: retire codec sessions on lease and fence events
Wake the cold cleanup worker when exported frames return and wait on returned fence readiness instead of polling every 10ms. Retain the notifier through slot publication so cleanup cannot free it during the final callback.
2026-10-27 10:40:52 +01:00
Codex 2f46e4c979 android: integrate hardware movie decoders with Vulkan imports 2026-10-27 10:40:52 +01:00
Codex 4b294e4ee8 video: release a retained worker window before main-thread activation
When video becomes unthreaded, retire the retained window before creating its replacement. Activating the replacement first can synchronously wait for the sleeping window owner. Keep same-thread window reuse and the existing cleanup wait unchanged.
2026-10-27 10:39:59 +01:00
Codex 55c6e9bda7 ci: recover VKMS kernel downloads from stalled mirrors
Fall back to the Ubuntu HTTPS archive using package URLs and strong checksums from apt metadata. Bound both transfer paths and retain download errors. Exercise mirror success, timeout recovery, invalid metadata and failed verification before the VM test.
2026-10-27 10:38:56 +01:00
LibretroAdmin a94a28cfeb r7z: PPMd, BZip2, Deflate and Deflate64 coders
7-Zip writes all four. PPMd runs the rrar var.H model behind 7z's
range decoder, BZip2 goes to rbzip2, Deflate to the built-in inflate,
and Deflate64 to rinflate64_decode, a one-shot decoder with the 64 KiB
window and the extended length and distance codes.

r7z_methods_test extracts 7-Zip archives of each, whole and in slices,
and runs a Deflate64 stream with matches past 32 KiB and lengths past
258 octets.
2026-10-27 10:25:41 +01:00
LibretroAdmin 17f017a0ff Android: a pen hovering does not hide the overlay; a setting keeps it
The on-screen overlay is hidden while a stylus is in use and comes back
two seconds after it was last used. Any pen event counted, a pen merely
hovering over the screen too, so the gamepad overlay went away as soon
as an S-Pen came near it, and "Hide Overlay When Controller Is
Connected" did not keep it (#19824).

A hovering pen now counts only where hovering moves the pointer ("Stylus
Hover Moves Cursor"); otherwise only a pen touching the screen does. And
"Hide Overlay While Using Stylus", on by default, can be turned off to
keep the overlay on screen while the pen is used, as melonDS does.
2026-10-27 10:11:57 +01:00
LibretroAdmin 7a2becd536 archive_file_7z: take members where the folder cache holds them
A member is decoded a step at a time into the folder cache and taken
from there, where a stored folder of a mapped archive is the archive
itself: extracted to a file it is written from where it is held, with
nothing copied, and read whole it is copied out once, into the buffer
the caller keeps.

archive_7z_test lists, reads and extracts a solid, a stored and a BCJ2
archive 7-Zip wrote through the archive layer, mapped and not.
2026-10-27 10:00:46 +01:00
LibretroAdmin c389bb9219 archive_file_zlib: entry sources for Zstandard and bzip2 members
An entry source opens for a zip member stored with Zstandard or bzip2
too. Such a member does not stop part way, so the first read decodes
all of it, from the mapping or what the stream lends where it can,
straight into the caller's buffer, which has room to the member's end,
and checks it against its CRC. The extract path decodes whole members
through the same two helpers.

archive_zip_test and archive_bz2_test read every member through an
entry source, and refuse one whose CRC is wrong.
2026-10-27 10:00:46 +01:00
LibretroAdmin 113f368557 archive_file_zlib: bound an entry source's member by the archive
An entry source takes its member's header offset and sizes from the
central directory, and read them from the mapping unchecked. They are
now bounded by the archive's size at open, as the extract init bounds
them: a member that does not lie inside the archive opens no source.
archive_zip_test opens sources for members stored too long, packed past
the end and with their header past it.
2026-10-27 10:00:45 +01:00
LibretroAdmin 7636b88a68 input: a controller touchpad's halves as two buttons
A DualShock 4's or DualSense's touchpad clicks as one button, where a
PlayStation game tells its left half from its right, often as Select
and Start (#19369). Controller Touchpad has a fourth setting, "Halves as
Buttons": pressed in, the pad presses the RetroPad button set for the
half its finger is on - "Touchpad Left Half" and "Touchpad Right Half",
Select and Start by default - and both with a finger on each.

It needs no new button numbers, so no profile or core changes, and
works for every driver that reads a touchpad (hid, sdl2, sdl3, udev).
Once a poll, where macros and remapped buttons are pressed, the click
and the fingers are read where the driver keeps them; nothing is
copied that was not before, and with no port in the mode a frame has
nothing more to do. The frontend harness's touchpad lane holds it to
the halves, both at once, no click, a button set by hand, and nothing
in another mode.
2026-10-27 10:00:27 +01:00
Codex 5046c41ec3 tests: declare browser walking and retain Windows compiler errors
Include the directory-walk declaration used by the shared wallpaper helper so GCC 16 can build the texture-menu tests. Capture Windows compiler output explicitly before reporting a failed build.
2026-10-27 09:09:20 +01:00
Codex 18cd961f51 media: retain transport state across video context resets
Save the current movie path, position and pause state before the viewer releases its player. Restore only the matching movie once its transport is ready, and discard saved state on media changes or cancellation. Cover delayed readiness and refusals in the shared controls tests, plus paused-position recovery through a real driver reset in all four menu harnesses.
2026-10-27 09:09:20 +01:00
LibretroAdmin ff305d5e54 r7z: hold a folder no coder changes where it lies
From an archive in memory, a folder that is stored, or only Copy, is
held as the folder cache in place, whole or decoded a step at a time:
a member borrowed is the archive's own octets, and one extracted is
copied out of them once. Copy hands its input on as its output in any
chain. A member detached from such a folder is a copy, being the
caller's to free.

r7z_stored_test borrows, extracts and detaches the members of a stored
archive 7-Zip wrote, from memory and through positioned reads.
2026-10-27 09:08:39 +01:00
LibretroAdmin 2ddc97ff80 archive_file: bzip2 zip members and .bz2 files
Zip members stored with method 12 are decoded with rbzip2, whole, from
the mapping, from what the stream lends or else read, as Zstandard
members are. A .bz2 file stands for the file named as it is without the
extension: a stream does not say how long it decodes to, so the member
is decoded when it is listed, its size and CRC then known, and kept for
the extraction that follows. "#" after ".bz2" names its member.

HAVE_RBZIP2, on unless configured off, builds both and the decoder;
RVZ's bzip2 groups take the decoder alone where it is off.
file_archive_whole() is the whole-file read the .zst and .bz2 backends
share.

archive_bz2_test lists, reads and extracts bzip2 zip members and a .bz2
file, and refuses members whose CRC is wrong.
2026-10-27 09:08:39 +01:00
LibretroAdmin d4ac05367c archive_file_zlib: a member that fails to decode is not read as empty
file_archive_compressed_read() of a zip member whose data is corrupt
or fails its CRC returns failure, not success with no octets.
archive_zip_test reads every member of its wrong-CRC archive whole.
2026-10-27 09:08:39 +01:00
LibretroAdmin ecb97e8ca6 rbzip2: walk a block from both ends, and decode into a growing buffer
A block's inverse transform is a chain of loads from anywhere in it.
Each entry is now linked to the one before it as well, and the block
walked from both ends at once, so one walk's loads wait alongside the
other's; the runs are undone in a pass after. A block that is mostly
long runs of rank 0, whose walk stays near itself, keeps the single
walk with the runs undone as it goes.

rbzip2_decode_alloc() decodes a stream whose size is not known into a
buffer grown as the blocks come.
2026-10-27 09:08:39 +01:00
LibretroAdmin fac37bac1e input: leave the controllers Steam Input stands in for
Steam Input, starting a game with a controller it translates, makes a
virtual pad of it and puts the real one's USB ids in
SDL_GAMECONTROLLER_IGNORE_DEVICES ("0x054c/0x09cc,..."), so that the
game reads only the virtual pad. SDL's games honour it; RetroArch's own
drivers did not, and read the controller twice: once as itself, once
through Steam.

The drivers that know a controller's ids at connect now pass over one
that is listed: winraw, DirectInput, the XInput and DirectInput driver,
udev, and the hid layer under every HID backend. A user can set the
variable too, for a controller another program mirrors. It is read
when a controller connects, never on a frame. ignore_devices_test holds
the list to how Steam writes it and SDL reads it.
2026-10-27 08:44:34 +01:00
LibretroAdmin 6e8f04953d r7z: BCJ2 folders read their packed streams where they lie
From an archive in memory, the packed streams feeding a BCJ2 folder's
ports, whether decoded whole or a port at a time, are read in place
rather than copied out first; only an archive that is read has them
read into buffers of its own.

r7z_bcj2_test extracts two BCJ2 archives 7-Zip wrote, from memory and
through positioned reads, whole and in slices, and counts what
extracting from memory allocates against reading.
2026-10-27 08:15:32 +01:00
LibretroAdmin 8235b3cc2f tools/console_objects_check.sh: see the crypto objects Makefile.common names
Object lists name libretro-common through $(LIBRETRO_COMM_DIR), so the
crypto check matched nothing; the variable is expanded before matching.
GameCube, Wii and PS2 may build crypto.o alone, the ciphers the
RetroAchievements hashes use, and are refused the KDFs, public-key
code and X.509.
2026-10-27 08:15:32 +01:00
LibretroAdmin d275a84a76 formats/rvz: bzip2 groups
Groups compressed with bzip2, in RVZ and WIA files alike, are decoded
with rbzip2; the decoder and its block buffer are made once for the
file. rvz_test writes RVZ and WIA images with bzip2 groups.
2026-10-27 08:15:32 +01:00
LibretroAdmin 7055db54b7 encodings: rbzip2, a clean-room bzip2 decoder
rbzip2_decode() decodes one whole bzip2 stream held in memory into a
buffer sized for it, checking every block's CRC and the stream's. Codes
up to ten bits decode in one table look, the CRC is taken eight octets
at a time, and the block buffer is kept from one stream to the next.
No file I/O.

rbzip2_test decodes streams bzip2 wrote and streams of every shape a
small writer in the sample makes, and refuses truncated, corrupted and
randomised ones.
2026-10-27 08:15:32 +01:00
LibretroAdmin 86a29b2e12 cheevos: rcheevos' AES on the crypto library
The 3DS hashes decrypt CIA titles with CBC and NCCH ExeFS sections with
CTR. Wherever crypto.o is built, cheevos/cheevos_aes.c serves rcheevos'
AES interface on it in place of the bundled software AES: the same
contexts and the same 128-bit counter, on AES instructions where the
CPU has them.
2026-10-27 08:15:31 +01:00
LibretroAdmin dffd47b1dd crypto: CBC decryption, eight blocks at a time
aes_cbc_decrypt() has no chain to wait on, so on AES-NI and the ARMv8
AES instructions eight blocks go through the inverse cipher at once,
with the InvMixColumns round keys made once per call. @in and @out may
alias, and the IV is left holding the last ciphertext block so a caller
can continue the chain.
2026-10-27 08:15:31 +01:00
Codex 967d4dcacf menu: bypass VC8 optimizer for dialog initialization
Limit the optimizer workaround to MSVC 2005 and restore optimization before the next function. Check both mixer configurations and adjacent compiler versions in CI, including a missing-guard failure control. The native VC8 build is still required to confirm the reported C1001 is resolved.
2026-10-27 08:12:53 +01:00
Codex 3b81720849 tests: allow surface fills to wait for GPU readiness
Use a bounded elapsed-time readiness check instead of sixteen unpaced iterations. Keep busy slots untouched when recovery times out.
2026-10-27 08:11:48 +01:00
LibretroAdmin 0f8abcc764 input: macros play under netplay
A macro was not started with netplay on: hosting with nobody connected,
one that was started never reached the core. It does now. A macro's
steps go in where a remapped button does, and netplay takes the local
player's input from there, as it is given to the core; so the host's
or a client's macro is sent and replayed like buttons pressed by hand,
for every player alike.

The frontend harness's macro lane hosts netplay and holds the core to
being given the macro as it is written, frame for frame; with the
macro kept from the core under netplay it fails.
2026-10-27 07:55:49 +01:00
Codex 9bbdf496d0 tests: bound 4K slot recovery by elapsed time
Unpaced frontend iterations can exhaust sixteen polls before a WARP upload returns its slot. Keep processing frames under a two-second watchdog outside submit timing, and require all sixty submits. Preserve the descriptor handoff and zero-copy budgets, and stop the lane on a real recovery timeout.
2026-10-27 07:33:20 +01:00
LibretroAdmin 1e64405a7d companion: Controller Shortcuts, the combinations and macros as text
The menu's hotkeys list edits the controller combinations
(input_combo_N) and macros (input_macro_N); the desktop companions had
no way to. View > Controller Shortcuts now lists them in Qt, Win32 and
Cocoa: each as the configuration writes it, by its number, then an
empty row to add the next under the lowest number free. A row's text
is set from what is typed; an empty text takes the number out; text
that does not read is refused and the row left as it was. How a row is
written, and what a combination can do, is shown beside it.

The rows come from the shared core (companion_core_shortcut_*), so the
three windows are thin. companion_core_test, and the Win32 and Cocoa
harnesses, add, refuse, and take out a combination through them.
2026-10-27 07:28:38 +01:00
LibretroAdmin 3263a29f6c archive_file_zstd: decode a .zst frame where the stream lends it
A .zst archive is opened with the hints every archive is, so it is
mapped where the host can afford it, and its frame is decoded from
filestream_view() when the stream lends it whole: the mapping, a
read-ahead window or a frontend's mapping. Only otherwise is it read
into a buffer. Extraction decodes from the transfer's own stream.
2026-10-27 07:27:19 +01:00
LibretroAdmin 290a32bc1a archive_file_zlib: decode zip members from what the stream lends
Deflate slices and whole zstd frames are taken from filestream_view()
where the stream can lend them, an smb:// or nfs:// read-ahead window
or a frontend's mapping, and read into the slice buffer only where it
cannot. A zstd member's buffer is allocated only when it is read.
2026-10-27 07:27:19 +01:00
LibretroAdmin c712f98982 rmsgpack: read a value from a window the stream lends
rmsgpack_read() takes each field from a window the stream lends with
the new intfstream_view(), and reads only what the window does not
hold. Buffered streams lend their window and file streams lend what
filestream_view() lends. The stream is left where the value ends.

Reading every entry of a 150,000-entry database through a cursor
goes from 1.31 to 1.61 million entries a second, with the same
values. The cursor is what the Explore menu and database lookups
read through.

rmsgpack_window_test writes values of every shape: integers of every
width and sign, and strings and binaries up to past the reader's
window. It reads them back through buffered streams with 4 KiB and
64-octet windows, a file stream mapped and not, and a memory stream,
which lends nothing. Every value must match and the position must be
right after each. It passes under ASan and UBSan.
2026-10-27 07:20:41 +01:00
LibretroAdmin 2b87454c1f formats/rvz: LZMA and LZMA2 groups, and WIA images
The reader takes RVZ groups compressed with LZMA or LZMA2, and WIA
images stored as they are, with PURGE, LZMA or LZMA2. LZMA and LZMA2
decode through r7z_lzma_stream and r7z_lzma2 with the group's output
as the window. An LZMA stream marks no end, so it is decoded as far as
the group's exception lists say, then its data. Builds without the 7z
backend link the two decoders for it alone. cheevos hashes .wia files
through the same reader. bzip2 groups still fail to read.

rvz_test writes LZMA streams with an encoder of its own that codes
every octet as a literal, through a real range coder and adaptive
model. LZMA2 streams mix stored chunks with chunks that keep the
model, reset it, or reset it with new props. It reads 27 images,
under ASan and UBSan, under qemu on big-endian PowerPC, and on 64-bit
Windows under Wine.

Comments in crypto.c, lrc_hash.c and cheevos.c now describe only what the
code does.
2026-10-27 07:20:41 +01:00
LibretroAdmin 76ab95a663 udev joypad: a pad's node that comes later is one case
A pad's motion sensors or touchpad can come after the pad. Two hotplug
paths took them, one per tag, and each made a udev device for every
connected pad to find whose HID device it shared. The pad keeps its HID
device's path now, and one path takes a node that comes or goes: it is
matched by that path and given its role as at connect
(linux_pad_members.h). A Wii Remote's accelerometer, which udev does not
tag, is taken when it comes after the pad too; it was missed.
2026-10-27 07:01:23 +01:00
libretroadmin 685c8f13b8 Vulkan temporal upscalers: order the library's work against every stage
The barriers around a DLSS, FSR or XeSS evaluate were scoped to the
compute stage: the output's last read ordered before a compute write,
its result made visible from compute, the core's inputs left to the
core's own fragment-and-compute barrier. Which stages a library uses
inside its evaluate is not ours to know, and on a GPU that overlaps
stages a write of the output from any other left the frame's display
sampling it part-written: now and then a frame shown without the
history, every edge aliased.

A full memory barrier now precedes each evaluate, the output's
barriers span all commands, and the output may be written by a
transfer. The Vulkan harness runs under the layer's synchronization
validation, with the stand-in library clearing its output through the
transfer stage, and records two frames in one command buffer: the
old barriers raise write-after-write hazards there, these do not.
2026-10-27 06:34:50 +01:00
LibretroAdmin e160e2ab67 udev joypad: a pad's other nodes, listed once under its HID device
A DualShock 4, a DualSense or a Wii Remote comes out of the kernel as
several nodes beside the pad's: motion sensors, a touchpad, raw reports.
At each connect the driver searched every input or hidraw device on the
system for each of them in turn - up to four scans - and kept those
whose HID parent was the pad's.

It now lists the pad's HID device's own nodes once, and a table
(linux_pad_members.h) gives each its role: the node tagged as an
accelerometer for the sensors, else the untagged ones in order, as for
a Wii Remote; the one tagged as a touchpad for the touchpad, never tried
as the sensors; the hidraw node for a DualShock 3's pressures. The
pad's own node takes none. linux_pad_members_test holds the choice to
that.
2026-10-27 06:16:53 +01:00
Codex 4e697b3b1d Android: convert SMPTE 170M hardware frames to display RGB
Decode extended Android primary/matrix pairs while retaining coded/native conflict checks. Convert SMPTE 170M SDR primaries to Rec.709 in the existing compute pass, writing sRGB or linear FP16 output. This admits the mixed color standard reported for untagged AV1 video without adding a pass or readback. Test metadata conflicts, unsupported HDR combinations and GPU pixels against a double-precision XYZ reference.
2026-10-27 06:16:04 +01:00
LibretroAdmin 1ac949abea formats/rvz: no file I/O in the reader; the caller reads what it asks
A format in libretro-common reads no file. formats/rvz opened its file
through filestream and read groups from it. Like rchd, the reader now
describes each range it needs as a request, and the caller supplies
it however it likes and says how much came:

   while ((err = rvz_open_step(rvz, &req)) == RVZ_PENDING)
      rvz_feed(rvz, pull(req.offset, req.dst, req.length));

   rvz_read_begin(rvz, offset, dst, len);
   while ((err = rvz_read_step(rvz, &req)) == RVZ_PENDING)
      rvz_feed(rvz, pull(req.offset, req.dst, req.length));

A request names where its octets go. Where a read can be served from
the file as it is - a raw group stored uncompressed and unpacked -
that is the read's own destination, so nothing is copied twice, as
before. A short supply is asked for again from where it stopped. A
supply of nothing fails the operation, and the next read goes on as
before. An operation that asks returns, and runs again from where it
stood: work done before the request (groups already decoded into
their slots, the open stages behind it) is kept. The file's size comes
from its head, which is hashed, for the bound on group ranges.

rvz_new()/rvz_free() replace rvz_open()/rvz_close(). cheevos.c, the
only user, reads the requests through the VFS, straight into the
buffers they name.

Also moves the padding generator's 2 KiB state and a hash block's
per-sector tables off the stack into the reader. Frames were over the
2048-octet budget the PSP and GX threads allow (rvz_block_load 2288,
rvz_junk 2160); the largest is now rvz_block_load at 752 (x86-64 gcc).

rvz_test makes and reads its files in memory, supplying every request
from there and now and then short. It checks:
- that requests are made into the read's own buffer (1798 of them)
- that a supply of nothing fails the read, and the read after it is
  whole
- that nothing is read before the image is open

It passes under ASan and UBSan, on 64-bit Windows under Wine, and
under qemu on 32-bit big-endian PowerPC. 900 runs on damaged files
through the memory supplier were clean.

rc_hash through cheevos' adapter, on the same images as before, gives
their ISOs' hashes. Wii is 8.4-9.3x faster than the old reader and
GameCube 1.03-1.13x, measured with the machine otherwise idle.
2026-10-27 06:11:47 +01:00
Codex 20288c5593 video_player: end empty movies without waiting for their duration
A decoder can finish or fail before presenting any picture, including after hardware recovery finds no software decoder. End these sessions without waiting for the movie-end deadline. Retain hardware recovery and the audio tail of movies that have shown a picture. Cover empty EOF and decode failure in inline and threaded playback.
2026-10-27 06:10:00 +01:00
libretroadmin 9d813ed695 Temporal upscaler: a frame time for the upscalers when the core gives none
DLSS and FSR weigh their history by the frame time delta they are handed.
A core that leaves it at 0 had the NGX backends pass 0 on, and FSR a
fixed 16.667 ms. With no time between frames the upscaler's weighting of
motion against time is undefined, and on a real display it showed as
single frames now and then presented without their history: the raw
jittered sample with every edge aliased.

The shared layer now supplies the period of the core's declared frame
rate when the core gave no frame time, which is what a presented frame
advances the content by whatever the clock did. The Vulkan and D3D12
harnesses check it reaches the evaluate.
2026-10-27 06:06:09 +01:00
LibretroAdmin b92a874a90 input: device groups, and Android's built-in controllers as rules
Some controllers reach the OS as more than one input device: the Xperia
Play's pad is two, the SHIELD keeps its NVIDIA button apart, the GPD XD
its back button, the ARCHOS GamePad its sticks. The Android driver
merged them with two global ids, pad_id1 and pad_id2, in a chain of
special cases: one such device at a time, two members at most, and
nothing done when one went away.

A device group (input/input_group.h) is one pad made of several OS
devices. The first member to arrive has the slot, later ones are read
as it, in either order. A member leaving keeps the pad; the lead leaving
hands the slot to the next member; the last leaving ends the group.
Several groups stand at once. A rule names the model, the members'
names and each one's role: part of the pad, the controller proper (a
second controller is a pad of its own), or a part that only joins, like
the NVIDIA button.

The Android driver's special cases are now a rule table
(android_groups.h) and the two globals are gone. input_group_test holds
the table to the old chain on every model and name it tries, and the
groups to join orders, members leaving, two groups at once and roles.
2026-10-27 06:00:33 +01:00
Codex f423d533ff tests: stage cloud-sync ownership before consuming downloads
Wait for the mock fetch callback without running the task consumer, so a completed download cannot commit before the fixture marks the save live. Keep the download pacing assertion under the same ordering. Use a local temporary directory and native path separators so the fixture also runs on Windows.
2026-10-27 05:57:16 +01:00
Codex 1a6254967a recording: include its command-event declarations directly
Pending recording startup uses CMD_EVENT_RECORD_DEINIT. Include command.h instead of relying on unrelated headers to expose the event enum; Orbis has no such transitive include.
2026-10-27 05:52:15 +01:00
LibretroAdmin 1a7b080dca cheevos: hash RVZ images through formats/rvz
cheevos_rvz.c is replaced by a small adapter in cheevos.c that hands
rc_hash the new reader's seek, tell and read. The console comes, as
before, from the disc header's Wii or GameCube magic word.

Against the old reader, on images whose ISOs are known:
- Hashes it got right come out the same. On Wii images rc_hash is
  7.2 to 7.9 times faster, with rcheevos' own MD5 now most of what is
  left. GameCube is about even, the time there being MD5's too.
- The old reader dropped a group's hash exceptions, so a Wii image
  with any came out with the wrong sectors. With chunks over 2 MiB it
  read one exception list too few from a short group, shifting that
  group's data by two octets, and the hash came out wrong. Both now
  come out as their ISOs do.
- It returned reads shorter than asked inside an image; rc_hash does
  not loop on a short read.

rc_hash keeps the file reader it was given from one load to the next.
After an RVZ, a disc image of another kind was opened with the RVZ
reader and its hash failed. The reader is now set on every load: the
RVZ one for a file it takes, the default otherwise.

The reader needs AES to encrypt Wii partitions again. A build without
the crypto library, Wii and Wii U among them, links crypto.o for it
alone, without HAVE_CRYPTO or the rest of the library. HAVE_CHEEVOS_RVZ
still follows HAVE_RZSTD, and configure now turns it off with cheevos
or RZSTD.
2026-10-27 05:52:05 +01:00
LibretroAdmin 50d33809a1 formats/rvz: a clean-room reader for RVZ disc images
RVZ is Dolphin's compressed GameCube and Wii image format. rvz_open(),
rvz_read() and rvz_close() read a file as the disc image it stands
for: every read returns the octets an ISO of the same disc has at that
offset. The code is C89, and was written from the format description
(WiaAndRvz.md), not from an existing reader.

- The disc is a sorted list of regions: raw data, rounded down to its
  sector as the format requires, and each Wii partition's two data
  segments. The header area comes from the disc struct, and gaps read
  as zeros.
- Groups are stored as they are or with Zstandard (HAVE_RZSTD). Groups
  of zeros are taken as zeros. RVZ packing is undone, padding included:
  the generator's state is kept mapped to the octets it stands for, so
  its output is copied out whole.
- Wii partition data is put back together a 2 MiB hash block at a
  time. H0 runs over each group's run of sectors with
  SHA1DigestMany(), a sector of zeros takes the zero KiB's hash, then
  H1 and H2 are computed and the hash exceptions applied. The hash
  areas and then the data are encrypted eight sectors at a time with
  aes_cbc_encrypt_lanes(). A disc whose header says it is not
  encrypted gets its hashes and no encryption.
- A raw group stored as it is, is read where it is asked for. A raw
  group read whole is decoded straight into the caller's buffer.
- The heads, the disc struct and the partition entries are checked
  against their SHA-1s, and nothing is read from past the end of the
  file.

samples/formats/rvz writes RVZ files from made-up discs and reads them
back, octet for octet, in order, at random and across the end. The
writer follows the description too: its padding generator is written
out as the description gives it, and it hashes and encrypts a sector
at a time. The discs cover:
- GameCube and Wii, chunks of 32 KiB to 4 MiB, NONE and Zstandard,
  stored and compressed groups mixed, groups of zeros
- padding cut at group ends and padding over many words
- one and two partition segments, the first a whole hash block and
  not
- encrypted and not, exceptions in the hashes and in their padding,
  and a short last hash block
A damaged head, a damaged disc struct and a cut-off file must not
open.

Each of these mutations of the reader fails the test: no exceptions,
the wrong padding shift, no NONE padding, the wrong IV, padding offsets
counted per segment, a short H2, unrounded raw data, one exception list
per group, and encryption ignoring the header. It passes under ASan
and UBSan, and on x86-64, 64-bit Windows (Wine), aarch64, armhf,
mipsel and 32-bit big-endian PowerPC. 3,600 runs on randomly damaged
files, the hashes made to agree, were clean under ASan and UBSan.
2026-10-27 05:52:04 +01:00
LibretroAdmin 13b8821d24 lrc_hash: SHA-1 over runs of blocks in place, and two messages at once
Every 64-octet block went through the context one at a time. Each was
copied into context->block, the state was loaded and stored around
it, and cpu_features_get() was asked again. The block functions now
take a run of whole blocks straight from the caller's buffer and keep
the state in registers across it. This applies on SHA-NI, the ARMv8
SHA1 instructions and the portable rounds alike. SHA1Input() hands
them whole blocks where they lie, and SHA1Digest() no longer goes
through the stream context at all.

SHA1DigestMany() hashes n messages of one length at a stride, the
form of a Wii sector's 31 H0 hashes, and of its H1 and H2 tables.
Where the hardware does the rounds, two messages run side by side,
because each round waits several cycles on the one before. Elsewhere
it is SHA1Digest() in a loop.

On SHA-NI (best of 7):
                         gcc -O2        clang -O2
  1 KiB messages      722 -> 949     735 -> 1006 MB/s
  one 2 MiB message   936 -> 1043    983 -> 1171 MB/s
  620 octets         1118 -> 1354    770 -> 1387 MB/s

test_hash checks SHA1Digest() and SHA1DigestMany() against the stream
fed in small pieces, at every padding boundary and with unaligned
strides, plus the million-'a' vector. It passes on SHA-NI and, under
qemu, on x86 without SHA-NI, aarch64 with and without the
instructions (built in and dispatched), armhf, mipsel and 32-bit
big-endian PowerPC.
2026-10-27 05:52:04 +01:00
LibretroAdmin d35ec19618 crypto: AES-CBC encryption, one chain or many side by side
aes_cbc_encrypt() encrypts a CBC chain and leaves the IV holding the
last ciphertext block, so the chain can be continued.
aes_cbc_encrypt_lanes() encrypts several independent chains at once.
A CBC chain waits on the cipher block by block. On AES-NI and the
ARMv8 crypto extensions, eight chains, four or one are interleaved
with each held in a register of its own, so the waits overlap. The
portable path encrypts one block at a time.

The RVZ reader is the first user: it encrypts a Wii partition's 64
sectors a hash block at a time.

The register form matters. With the chains in an array indexed by a
count known only at run time, gcc kept them on the stack, and every
round of every chain waited on a store and a load besides the cipher.
That ran at 2.4-2.7 GB/s on eight 32 KiB chains. Held in registers
they run at 6.3-7.5 GB/s, and clang, which promoted the array itself,
gains about 2%.

test_crypto checks SP 800-38A F.2.1, then 1 to 13 chains with 128 and
256 bit keys, in place and not, against a block-by-block reference.
It passes natively under ASan and UBSan, and under qemu on aarch64
(with and without the crypto extensions), armv7 with NEON, armv6k and
mipsel.
2026-10-27 05:52:04 +01:00
LibretroAdmin d4d1823632 rzstd: put the recent offsets back when a block is stored raw
The encoder searches a block for matches, then stores it raw instead
when compressing it does not come out smaller. The search moves the
three recent offsets the decoder keeps, and a raw block leaves the
decoder's as they were, but the encoder's stayed moved. The next
compressed block could then name an offset by a repeat code that the
decoder resolved to a different offset. The frame decoded, here and
with the reference decoder, to the wrong octets and with no error.

That takes a block of noise containing a match the search finds, too
short to pay for the block, followed by a compressed block that
repeats at that match's distance. RVZ groups of Wii partition data
built that way came back wrong. The bsv replay writer, rzip and
trans_stream compress through the same encoder.

The offsets are now saved before the search and restored when the
block goes out raw. samples/encodings/rzstd has the three blocks that
showed it. Every level failed there before the fix and passes after.
2026-10-27 05:52:04 +01:00
LibretroAdmin 88362bc790 smb_retro: let the AD DC's processes go before removing its directory
The cleanup killed the domain controller and slept half a second
before removing its directory. Samba's children can still be writing
under it then, and the removal failed with "Directory not empty". That
failure became the script's exit status, so a matrix that had passed
was reported as failing.

The cleanup now waits, up to five seconds, until no process names the
directory, killing whatever is left, and then removes it. The script
exits with the matrix's own status, not the removal's.
2026-10-27 05:52:04 +01:00
LibretroAdmin ad2e12e0c4 net_smb2: receive READ data straight into the caller's buffer
A pipelined fetch reads each reply's header and READ response first
and, for a successful READ of one of its requests, receives the data
where that request wants it in the caller's buffer. A signed reply's
signature is checked over the pieces; a sealed one decrypts its
header aside to route by, and its data where it landed - after the
tag for AES-GCM, with the MAC for AES-CCM. Every other message is
received whole into the connection's buffer, as before.

The SMB fuzz target opens and reads a file too, from a seed taken
from a guest session that reaches the READ.
2026-10-27 05:52:04 +01:00
LibretroAdmin cd6f246027 fuzz: clear the input when an iteration ends
The listener copies the input for each connection it accepts, but a
connection accepted after the iteration that made it returned - a
client that gave up, its connect still queued - copied an input the
fuzzer had freed. The TLS, SMB and NFS targets now clear the input
at every exit, so such a connection is answered with nothing.
2026-10-27 05:52:04 +01:00
LibretroAdmin 98465df30e crypto: CMAC, CCM and GCM over a message in pieces
aes_cmac_init/update/final, aes_ccm_stream_* and aes_gcm_stream_*
take a message in pieces that need not be blocks. The GCM stream
hashes the AAD and ciphertext piece by piece and runs the counter
mode at any octet offset, so pieces can be decrypted where they lie
after the tag is checked; GMAC is its AAD alone. CCM decrypts or
encrypts in order with the MAC alongside. They keep their chaining
state in locals for the duration of a call, and run as fast as the
one-shot forms or faster under gcc -O2, clang -O2 and gcc -Os.

test_streams checks each against its one-shot form over 400 random
messages cut into up to three pieces anywhere, empty ones included.
2026-10-27 05:52:04 +01:00
libretroadmin 345140315a Revert "TRACE: temporal upscaler per-frame debug log (diagnostic, not for merge)"
This reverts commit 87199b6143.
2026-10-27 05:51:09 +01:00
LibretroAdmin 71944e29df input: read the original Xbox pad, and move it to WinUSB on Windows
An original Xbox pad, or an adapter for one, is an XID device (USB
class 0x58), not a HID one, and no driver of Windows' runs it. A new
handler reads its 20-byte report: the D-pad, Start, Back and the stick
clicks as bits, the six face buttons and both triggers as a pressure
each, and the sticks. A face button or trigger counts as pressed past a
third of its travel; its pressure is an axis, which the built-in
profile binds, so a core that reads pressure has it. Rumble goes out as
output report 0 from the packet handler, when it changes.

On Windows the WinUSB scan takes such a pad by its interface class,
whoever made it, except the DVD remote's receiver and Steel
Battalion's controller. One with no driver at all is said once on
screen, and Information > Input Information offers to move it to
WinUSB, as for the Wii U GameCube adapter; one on XBCD or libusb is
left where it is.
2026-10-27 05:47:51 +01:00
libretroadmin 87199b6143 TRACE: temporal upscaler per-frame debug log (diagnostic, not for merge) 2026-10-27 05:36:44 +01:00
Codex b77c31fbed tests: keep native Windows results authoritative
Run driver and media acceptance directly and replay failures under GDB for diagnostics, retaining the original exit code and captures. Keep GDB=1 for explicit debugger runs. Link the media harness as a console program so its output reaches CI without a debugger. Both the unchanged baseline and candidate pass natively but can fail under GDB; do not substitute the diagnostic replay result for the primary run.
2026-10-27 05:11:10 +01:00
Codex 43580f5b9e recording: open output after startup state has settled
Defer recording until the first core frame after pending startup state loading. This lets the core publish its resulting AV information before the output is opened, preserving the requested filename instead of creating a header-only file and restarting into a dated recording. Cancel pending startup on stop, content close and quit. Exercise the actual startup functions and retain the auto-state-load harness link.
2026-10-27 05:11:10 +01:00
Codex ac57896750 Android: align RELRO for every 64-bit flavor
Quest sets PLAY_STORE_BUILD, which excluded it from the explicit 16 KiB linker settings. NDK 29 aligned its LOAD segments but left RELRO ending at a 4 KiB boundary. Apply both maximum and common page sizes by ABI for every flavor, including Quest and Play Store builds. Keep the APK alignment checks unchanged.
2026-10-27 05:09:06 +01:00
Codex 5078a39b62 Vulkan: avoid corrupt H.264 decoding on RTX 5090 driver 621.01
Independent Vulkan decoding reproduces displaced reference-picture blocks on this device and driver, while software and D3D11 decoding match the reference. Use the existing decoder fallback without changing the Vulkan renderer. Keep the earlier 610.47 AV1 workaround separate and test neighboring device and driver admission.
2026-10-27 05:00:33 +01:00
libretroadmin 00a3de3cd7 Temporal upscaler: HAVE_TEMPORAL_UPSCALER gates the subsystem
HAVE_NGX gated the whole temporal upscaler - the interface handed to
cores, the settings, the threaded wrapper's carry and the DLSS, FSR
and XeSS backends alike - under the name of one vendor's library. A
MetalFX backend on the Metal driver would have to be built under a
define that reads as NVIDIA's. The subsystem is now HAVE_TEMPORAL_
UPSCALER wherever HAVE_NGX stood, in the build files, the drivers, the
menu, the samples and the compile matrix; nothing changes in what is
built.
2026-10-27 04:52:50 +01:00
libretroadmin efd62fa8a8 Threaded video: the upscaler's inputs go with their frame
A core sets the temporal upscaler's inputs, then pushes the frame, and
the driver takes them inside its frame call. Under the threaded video
wrapper that call runs on the video thread, later: by then the core
has set the inputs of a newer frame, or they went to the frame before.
A frame was upscaled with another frame's jitter and motion vectors,
and the next shown without upscaling at all, raw and aliased: the
black and white outlines flashing in Ocarina of Time under DLSS.

The wrapper copies the inputs into its hardware ring slot at the push,
on the core's thread, and carries the copy to the driver on the video
thread before the frame call; the driver's takes then come from the
carry alone. The threaded video harness gains a lane that pushes
hardware frames with inputs behind a slow present and checks that
every frame drawn took its own.
2026-10-27 04:12:29 +01:00
Bradleyw801andClaude Opus 5.5 3b233712a4 Android: ask for unbuffered stick and touch input (#19925)
Android batches motion events and hands them to the window at the next
display frame, so a stick move or a finger drag reached the input
queue up to a frame late. Ask for unbuffered dispatch of joystick and
pointer sources (API 30+), posted to the decor view since the request
only takes on an attached view.


Claude-Session: https://claude.ai/code/session_01YTBjv1trXRxAXvNwCt89Ka

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-27 04:14:41 +01:00
Bradleyw801andClaude Opus 5.5 4fb6dc6834 Android: read a port's buttons from the pad it is mapped to (#19924)
android_joypad_state() read the hats and axes of the pad mapped to the
port (joy_idx), but the buttons of the pad that has the port's own
number. With a port's device index set to another pad, the stick and
d-pad hat followed the setting and the buttons did not.


Claude-Session: https://claude.ai/code/session_01YTBjv1trXRxAXvNwCt89Ka

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-27 04:12:57 +01:00
c6046da20f Android: read the input queue on a thread of its own (#19923)
* Android: read the input queue on a thread of its own

Android sends a window its next key only once the window has finished
the last one, and a NativeActivity gets every key twice: before the
IME, marked AKEY_EVENT_FLAG_PREDISPATCH, and after it. Read on the
main thread once a frame, with the first copy finished unhandled for
the IME, each key took two polls, about 30 keys a second at 60 Hz. A
pad mashed faster than that queued up in the system, and its presses
kept arriving seconds after it was let go.

A thread with its own looper now reads the queue. It copies each event
into an SPSC ring and finishes it at once, so Android sends the next
one straight away. The copy before the IME is the one read, unless the
system keyboard is open, when keys still go by the IME first. Volume
keys are left to the system and read once, after the IME. Until the
first input driver starts, events are finished unread. With the ring
full, the reader leaves events in the queue until the poll makes room.

The poll applies the copies in order through the same handlers. A key,
a finger or a mouse button can now go down and back up between two
polls, so the poll stops at the first event that would undo what an
earlier one did in the same poll and leaves the rest for the next
(android_poll_changes.h): a press shorter than a frame is still seen
for one. Mouse moves in one poll are summed.

The reader answers APP_CMD_INPUT_CHANGED once it has let go of the old
queue. The AMotionEvent calls looked up with dlsym are looked up once,
before the reader starts, since it reads them too.

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

* Android input reader: records of their own size, read where they lie

Each event went through the input reader's ring as a 312-byte record,
whatever it was, built on the reader's stack, copied into the ring and
copied out again by the poll. A key needs 44 bytes of it and a stick's
move 88.

A record is now as long as its event: a key ends before the stick
axes, a stick's move before the touch fields, and a touch, mouse or
pen carries only the pointers it has. The reader builds each where the
poll will read it, and the poll applies it there
(android_input_ring.h); nothing is copied after the event leaves the
input queue. A record never crosses the ring's end: the rest of a lap
is a pad the poll steps over. The ring is 64 KiB, some 1500 keys.

A stick's moves are its state: the poll keeps only the last of a frame.
The reader publishes what it writes once the queue is empty, and a
stick's move written over its unpublished move before it, as long as
the hat stays where it was, so a fast pad no longer fills the ring and
holds back keys behind it.

A hat, the D-pad of many pads, tapped between two polls was lost: the
poll's hold-back covered keys, fingers and mouse buttons only. Its four
directions are kept back the same way now.

android_input_ring_test holds the ring to reading in place, records
whole across laps, merging only unpublished moves, and two threads;
android_poll_changes_test has a hat lane.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-27 04:10:15 +01:00
Codex 8afa29fd61 video_player: recover hardware failure before its movie deadline
Handle a terminal hardware job before scheduling its timestamp when no picture has been presented. A failed first job can carry a future timestamp and must not postpone software recovery or end-of-stream reporting.

Cover recovery and missing-fallback paths with a failed job timestamp thirty seconds ahead of the polling clock.
2026-10-27 03:43:13 +01:00
LibretroAdmin 52937c89f4 input: clear the snapshot bridge's views one by one
The view table holds pointers to const pad views, which MSVC's memset
warns about (C4090). Each slot is set to NULL instead.
2026-10-27 03:25:53 +01:00
LibretroAdmin 901872dd45 UWP: define retro_vfs_file_view_impl
The UWP backend replaces vfs_implementation.c, so filestream_view()
needs its own definition there. It lends from an smb:// stream's
read-ahead window; local files are never mapped on UWP, so everything
else answers -1 and the caller reads.
2026-10-27 03:24:09 +01:00
LibretroAdmin fa0ceb469f file_stream: filestream_view(), a read that lends instead of copying
filestream_view() points at up to len octets at the stream position
and moves past them, as filestream_read() would after copying them:
from the gets/getc lookahead, from the file's mapping, from a
frontend VFS's mapping, or from an smb:// or nfs:// stream's
read-ahead window. -1 lends nothing and moves nothing, and the
caller reads as before.

The scan CRC step, sha1_calculate(), the cloud sync MD5 and
filestream_matches_buf() take what can be lent where it lies and
read the rest, so a hash over a share with read-ahead on no longer
copies what the prefetcher already holds; on a mapped file they
consume the mapping as before. A mapping over 4 GiB is hashed in
blocks rather than passed as one length the hashers truncate.

vfs_view_test covers the lookahead, mapped, unmapped, update-stream
and frontend-mapping cases and sha1_calculate(); the smb:// and
nfs:// thread tests read a file through lends against its contents
with read-ahead off and on.
2026-10-27 02:09:52 +01:00
Codex dc34e9b166 Android: preserve 16 KiB alignment in sideload builds
Use 16 KiB maximum and common linker page sizes on arm64 and x86_64 so sideloaded libraries do not require the Android compatibility mode. Check LOAD and RELRO segments in the media-enabled frontend and packaged 64-bit APKs, with ZIP alignment validation.
2026-10-27 01:41:32 +01:00
Codex de6ddfc451 griffin: link AV1 hardware registry in unity builds
Include the AV1 backend registry when HAVE_RAV1 is enabled. Media-enabled Android unity builds otherwise leave its registration and session entry points unresolved.

Add a complete Android media link to CI so missing unity dependencies are caught alongside the individual source checks.
2026-10-27 01:12:44 +01:00
Codex fce80847e3 tools: audit Dispmanx overlay setter bounds
Register Dispmanx in the overlay setter checker. Its geometry and alpha
setters already guard the image index against the loaded page count.
2026-10-26 22:53:25 +01:00
LibretroAdmin 2538f1aa0c sunxi: screenshots under the menu are of the menu
read_viewport reads the menu's frame while the menu is up, as that is
what the layer shows. Lane in samples/gfx/sunxi_display.
2026-10-26 22:50:33 +01:00
libretroadmin be766c60fa Temporal Upscaler: DLSS takes the core's transparency mask
A core's transparency mask, the pixels to take from this frame over the
history (particles, translucent effects), went to DLSS as its
TransparencyMask, a parameter DLSS reserves and never reads, so such
pixels kept their history and left trails behind them. It now goes to
DLSS as the current color's bias mask, which DLSS weighs them by, on
Vulkan and Direct3D 12 alike.
2026-10-26 22:50:07 +01:00
LibretroAdmin cc8df813d5 file_stream: format filestream_printf() into a buffer of the stream's
filestream_vprintf() formats into a buffer the stream owns, grown to
the longest line printed on it and freed at close, so threads printing
to their own files share nothing and a line of any length is written
whole. A line past the buffer is formatted once more into room for it.

filestream_printf_test writes lines around and past the first buffer
and prints from two threads into two files, each checked for exactly
its own text.
2026-10-26 22:46:54 +01:00
LibretroAdmin f7f71c192f chd_stream: decode ahead on a thread, lending odd hunks in place
A stream read on sequentially - one read call spanning two hunks, or
32 hunk loads in a row a sector at a time - starts a vfs_prefetch
thread with a file and an rchd decoder of its own. The thread decodes
the odd hunks, one window each, and the reader the even ones, so the
two split the work rather than race for it: a whole-track hash or CRC
decodes on two cores. A ready odd hunk is read from its window where
it lies; a miss decodes it on the reader as before and retargets the
thread. Audio tracks are swapped on whichever side decodes. Nothing
starts on one core, or for the few sectors an identification reads.

chd_stream_ahead_test reads data and audio tracks long enough to
start the thread - in bulk, a sector at a time and across seeks -
against their sector contents, and closes streams mid-decode.
2026-10-26 22:46:49 +01:00
LibretroAdmin 690c5d5d2f chd_stream: a pump buffer per stream, and getc reads its octet
The buffer rchd's requests are read into before they are fed belongs
to the stream, allocated at open: streams open and read on whichever
thread holds them, so two CHDs read at once - a scan and an
achievement hash, say - no longer share one. chdstream_getc() reads
the octet it returns and reports EOF at the end of the track.

chd_stream_threads_test opens and reads two images on two threads,
each checked against its own sector contents.
2026-10-26 22:46:45 +01:00
Codex 35aeab84c3 record: honor cancellation during final GPU retirement
Recheck cancellation after capture retirement before publishing the final
state, preserving earlier errors. Gate capture release in the dispatcher
regression so cancellation at this boundary cannot report success.
2026-10-26 22:26:59 +01:00
LibretroAdmin 04890bb46d sunxi: lay out, turn, pace and lend frames; fail cleanly off sunxi
The layer's window is where the frontend lays the frame out - aspect,
integer scale, custom viewport, cut where it leaves the screen - set
only when it moves. A rotation is turned by the CPU into the page. A
frame is drawn only into the page not on the screen, as the vsync
thread now only reports the flip that landed; without vsync a frame
meeting a flip in flight is dropped, and a framebuffer that cannot wait
for a vsync ends the thread instead of spinning. An XRGB8888 core is
lent the page to be shown next. Messages and statistics are blended
into the page; screenshots read it back.

Pages are XRGB8888 past the console whatever its depth, laid out only
where the framebuffer has room for two; the menu converts at the end
of the framebuffer, past them, and shows before any core. Init fails
cleanly off sunxi hardware or without a free scaler, and the layer
ioctls pass their arguments as the kernel reads them, unsigned long.
Lanes in samples/gfx/sunxi_display; docs/video-driver-sunxi.md.
2026-10-26 16:54:35 +01:00
LibretroAdmin 726510fddc gfx: xshm, omap, sunxi and drm turn each view under a rotation
The CPU drivers composing a core's views give the compositor the
rotation the layout was made for, so each view is turned in its own
place: drm from its own rotation (its views surface is never turned),
the others from the frame path's rotation snapshot. omap_init no longer
reads settings it never used. Lane in samples/gfx/drm_surface_lifetime.
2026-10-26 16:54:31 +01:00
LibretroAdmin ed798b5144 gfx: CPU-composed views turn each view in its own place
video_views_cpu_t carries the quarter turns its views are drawn at:
each view is turned before it is scaled into the placement the layout
gave it for the core's rotation. dispmanx sets it; the other CPU
drivers leave it at zero. Lane in samples/gfx/video_views.
2026-10-26 15:54:44 +01:00
LibretroAdmin 60b3380f9a dispmanx: on-screen messages and statistics on layers of their own
The message and the statistics are each drawn at the screen's
resolution on a DispmanX layer at the top, as large as the text: drawn
and uploaded only when the text, its place or its colour changes, into
the one of two resources not on the screen, which the next flip shows.
A layer that outgrows its resources gets new ones; the old ones go once
the flip that took them off has landed.
2026-10-26 15:46:31 +01:00
LibretroAdmin 5c30ef7817 dispmanx: input overlay on HVS layers, display by monitor index, screenshots
Each overlay image is an element of its own over a resource uploaded
once at load; the HVS crops, scales, mirrors and blends it, and a move,
fade or toggle changes the elements in the next flip's update. An image
switched off, transparent or off the screen has no element.

video_monitor_index picks the firmware display (default, HDMI 0, HDMI 1,
LCD, composite), falling back to the default when it is not there; the
screen size is asked of that display. Screenshots snapshot the screen as
composed and cut it to the viewport.
2026-10-26 15:45:33 +01:00
LibretroAdmin 8c93843ffb dispmanx: lay out, turn, filter and lend frames as the frontend asks
The frame's element goes where the frontend lays the frame out -
aspect, integer scale, custom viewport, cut where it leaves the screen -
and moves there with the next flip. A core's rotation is a quarter
turned by the CPU and a half by the HVS. video_smooth is taken at run
time and a nearest kernel is put back on exit; a smooth start leaves
config.txt's kernel alone. Without vsync, a frame that meets a pending
flip is dropped rather than waited for.

Rows reach the firmware at its own pitch: a lent framebuffer and the
threaded wrapper's frame memory are laid out at it, other frames once
into the surface. RGUI blends by its own alpha on a layer above the
frame. Lanes in samples/gfx/dispmanx_display; docs/video-driver-dispmanx.md.
2026-10-26 15:35:44 +01:00
LibretroAdmin 0b76e325fd gfx: one CPU quarter turn for drivers whose display cannot turn
gfx/video_turn.h holds the turn drm_gfx.c had, so dispmanx uses the
same one.
2026-10-26 15:35:40 +01:00
libretroadmin dda31fef21 Temporal Upscaler: DLSS loads once; Neural Rendering toggles in place
DLSS's feature libraries, Super Resolution's and Neural Rendering's,
stay loaded once NGX has loaded them, so NGX set up again on the next
content's device or after a driver restart finds them in instead of
loading and checking them again. NGX's start-up, and the making of each
feature, is timed in the log.

Turning Neural Rendering on or off no longer restarts the video driver:
its device extensions are asked for (once a run) whenever its library is
in the DLSS directory, and the core asked about it then too. Turned off,
its model is kept, and turning it on again takes the next frame.
2026-10-26 15:10:08 +01:00
libretroadmin 11dc3997e8 vfs_prefetch: lend ready windows in place, and build it always
vfs_prefetch_view() is vfs_prefetch_read() without the copy: it points
at the octets in a ready window and returns how many follow there. Only
the reader recycles windows, so the pointer stays valid until its next
read, view or seek. A reader that decodes ahead into windows (a core's
CHD hunks, one window each) can then hand out sectors where they lie.

The prefetcher is built in every configuration, griffin included,
rather than only with the network backends: statically linked cores
read ahead through it too. Without HAVE_THREADS it is stubs.
2026-10-26 14:48:45 +01:00
libretroadmin 11370445cf Temporal Upscaler: DLSS takes frames in any of its modes; less at start-up
A core that renders in steps, such as a multiple of the console's
resolution, can land outside the one DLSS mode's range and was shown
without upscaling, saying nothing. The request now names the range DLSS
takes over all its modes, and a frame outside its mode's runs in the
mode whose range it is in, the nearest to its size; a frame no mode
takes is said once in the log.

NGX's extension names are kept for the life of the process instead of
asked again, with its feature libraries loaded and checked, every time
a context is made. Neural Rendering's library is asked about only while
it is turned on; turning it on or off now restarts the video driver.
2026-10-26 14:23:52 +01:00
LibretroAdmin 995b77df3e content_load harness: MIDI lanes play notes through every core lifetime
The harness core plays through the MIDI interface it is handed, a
note-on every retro_run while output_enabled() says so and a note-off
from retro_deinit, and a counting driver installed in place of "null"
records device opens, frees and the notes that reach it.

midi boot: a core asking in its load at boot (-L, before the first
drivers_init) plays through the device. midi requested: a core asking
during a staged load, on the old session's drivers, plays through the
rebuilt ones and keeps playing across a CMD_EVENT_REINIT (the
DRIVER_LIFETIME_RESET path SET_SYSTEM_AV_INFO takes); its note-off at
unload reaches the device; after closing content and after starting a
core that does not ask the output is shut with every open freed.
2026-10-26 13:53:15 +01:00
LibretroAdmin 27ed906296 midi: a core's MIDI request lasts until the core is unloaded
A staged content load runs the new core's init and load on the
previous session's drivers, then rebuilds them. The core's
GET_MIDI_INTERFACE request now ends in runloop_event_deinit_core after
retro_deinit rather than in driver_uninit, so drivers_init reopens the
device on the rebuilt drivers for a core that asked during its load.

The content_load harness gains a lane for it: a core asking at load
has the output open after the load, the next core that does not ask
has it shut.
2026-10-26 13:45:44 +01:00
Davey HughesandLibretroAdmin ad2e9e05d8 scan: parallel readers, 3DS product codes, CRCs and index builds over steps (#19920)
* samples/database: include compat/strl.h in the scan test

The test calls strlcpy(), which mingw-w64 does not declare, so it did
not build for Windows.

* PS1 scan: PSX.EXE means no serial only as the BOOT target

A root directory can list a PSX.EXE ahead of the executable SYSTEM.CNF
boots: Tomb Raider (USA) lists one before SLUS_001.52. Stopping at the
first PSX.EXE in the probe window sent such discs to a whole-track CRC,
eight seconds for a CHD. Look for the BOOT target first, matched without
regard to case so cdrom:\psx.exe counts. A disc that boots PSX.EXE, or
has no SYSTEM.CNF so the BIOS boots PSX.EXE, has no serial and still
falls back to its CRC (#15241); any other disc is searched for its
serial with PSX.EXE directory records ignored.

* 3DS scan: identify .3ds, .cci and .cxi by the NCCH product code

These images took the default whole-file CRC, 0.5-4 GB a game. The
product code in the NCCH header (partition 0 of a cart image, or the
start of a bare NCCH) is the serial the 3DS database is keyed on, and
the header is plaintext in encrypted dumps too. A code held by one
record matches it outright. Revisions and regions that share a code fall
back to the file's CRC to choose, and to the first record when no CRC
matches; with scan_serial_and_crc on every match is confirmed by CRC, as
for PSP and Dreamcast, and a file none of whose records carry its CRC
goes on to the plain CRC lookup with the CRC already computed. A code no
record carries, or a header without valid magic such as a CIA, keeps the
CRC path, in first-match mode too. Like the CRC lookup, the serial pass
visits only databases an installed core claims the extension for, unless
Scan Without Core Match is on.

* scan: run ITERATE steps while the shared I/O window lasts

A file takes about twenty ITERATE steps, and the handler ran one per
call. On the regular task queue that is one step per frame: 1000 NES
zips took 335 s of pacing, 400 arcade sets 1639 s. The ITERATE states
now run in a loop under the same window the BEGIN states use, which
times the steps rather than counting them and grants every call at least
one. The scan test's unclaimed-database lane spends the window before
each check, so that it still counts steps.

* scan: hash file CRCs a quantum at a time across steps

A step that needed a CRC computed all of it in one handler call: a whole
CHD track with LZMA decode, or a multi-gigabyte 3DS image. On the
regular queue that froze a frame, and on the threaded queue it held the
worker for seconds. Each CRC site now opens its span and returns, and
later steps hash 256 KiB quanta while the shared I/O window lasts, at
least one per call. The lookup that wanted the CRC runs when it is in,
at the same entry. The one-shot CRC helpers, which nothing else called,
are gone.

* scan: walk each database once for its crc and serial indexes

The first file to reach the serial lookup built a crc index and then a
serial index for every database, a full walk each: 47 MB of Mobile -
J2ME twice in one handler call, the second for an index that came out
empty. The crc walk now reads serials as well. It notes whether a
database has any, so the serial lookup passes over one with none, and
for the serial lookup it builds the serial index in the same pass. Index
contents, matches and the split of the index budget are unchanged.

* scan: let a database's indexes be built a run of records at a time

A database's crc and serial indexes were built by one walk and a qsort
in a single call: 50-70 ms for Mobile - J2ME. libretrodb_scan_begin(),
_step() and _end() walk a run of records per call, and
database_info_index_job_*() builds both indexes over as many calls as it
takes, each walking a run of records or merging a run of entries in a
resumable merge sort. libretrodb_scan_field2() and
database_info_index_new() are built on them and answer as before.

* scan: build each database's indexes over steps

The first file to reach a database built its indexes in one handler
call: 52-68 ms on the regular queue for Mobile - J2ME, 13-17 frames'
worth of the 4 ms window. The build now runs over the steps that follow
the lookup that began it, a run of records walked or entries merged at a
time while the window lasts, and the lookup takes the indexes when they
are done. A database counts as size-checked only then. Cancelling frees
a build in flight. The one-shot builders, database_info_crc_index_new(),
database_info_serial_index_new(), database_info_index_new() and
libretrodb_scan_field() and _field2(), lose their last caller here and
go; the tests build through the job and the stepped scan.

* task_queue samples: build on macOS, run on GCD in a macOS job

No sample ran the GCD runner and nothing in CI built HAVE_GCD. Apple's
ld has no --wrap, so on Darwin the switch and init-failure tests
redirect the wrapped calls from task_queue.c by macro instead, and
libdispatch comes from libSystem there. The switch test gets a GCD=1
build, and a macOS job runs every task_queue sample plain, under
ASan+UBSan and TSan, threaded and, where it has a GCD=1 build, on GCD.

* task_queue: a switch waits out a wake from another thread

task_queue_wake() is documented as safe from any thread, but a switch
between the threaded and the unthreaded queue frees the threaded
runner's lock and condition variable while a waker on another thread can
still be inside it. Toggling Threaded Video makes that switch at the
next frame. Two seq_cst counters now keep them apart without a lock: a
switch that finds a waker inside is put off to the next
task_queue_check(), and a waker that finds a switch under way leaves its
task to it. A wake on the thread that drives the queue cannot meet a
switch and skips the counters.

A switch now ends every park, and the new runner takes wakes again
before it runs any task, so a parked task loses nothing to a wake
dropped during the switch. A wake dropped while a switch is put off and
then taken back, or for a task whose park a switch keeps because it
leaves a stuck handler behind, waits for that park's horizon.

The switch test holds a waker inside the queue across a switch, wakes
from four threads through 1000 switches, and holds the main thread where
the new runner first runs a task while that task's first pass wakes it.

GCD builds leave out the threaded runner's own code, which they never
used and which warned there. The switch test also checks that its
realloc() wrapper ran, since on macOS that wrap is a macro rather than a
link option.

* scan: a scan of one file only adds to its playlist

A Content Location that is a file is scanned as one file, which the Scan
button, --scan=FILE and iOS "open in" already rely on. Such a scan
cleared the playlist when Overwrite was on, and recorded the file, and
its core, as what the playlist refreshes with, so Refresh Playlist later
rescanned that file alone. Overwrite is now ignored for a file, and the
playlist's scan record and default core are left as they were. Validate
Existing Entries is skipped too: a file scan's extensions can differ
from its folder's, so validating with them could drop entries the folder
scan added. A later folder scan or Refresh still prunes. With no
database and an automatic target playlist, a file is filed under its
folder's name, as a scan of that folder files it, rather than in a
playlist named after the file. A Content Location that is missing, as on
a drive that is offline, counts as a file, so Overwrite no longer
empties its playlist while the drive is away.

* menu: Content Location takes a file or a folder

Content Scan's Content Location now opens one browser that lists folders
and files in every scan method: OK on a file picks it, <Use This
Directory> picks the folder. In Automatic it lists every file, as an
Automatic scan reads every file; Custom keeps listing the scan's
extensions. The Scan Single File toggle, which switched the browser to
files and was shown only in Custom with Search Recursively off, goes
with its setting and strings, and a scan of a file no longer turns
Search Recursively off behind the user's back. Import Content drops Scan
Directory and Scan File, as the Scan Directory list already had, leaving
Content Scan as the one way in; Scan File no longer started a scan. On
Apple, File > Open and the iOS document picker opened from Content
Location set the location as OK on a file does.

* task_queue: hold pushes from other threads for the main thread

task_queue_push() from another thread could meet a switch between the
threaded and the unthreaded queue, which frees the lock and condition
variable the push takes. On the unthreaded queue, with no switch at all,
it put the task on a list the main thread walks without a lock. Windows
pushes from the video thread under threaded video, and HID builds from
libusb's thread. Such a push now goes through the switch's gate: when
the gate is shut, or the unthreaded queue is up, the task goes onto a
lock-free list that the next gather pushes on the main thread, and the
push returns true. A push is also held while earlier ones still wait, so
a thread's pushes start in the order it made them. A BLOCKING task
refused there is cancelled, and its callback still runs. The switch test
pushes from four threads on the unthreaded queue and through 1000
switches, checks a held BLOCKING push, and holds the main thread inside
the gather that reopens the queue to show that a thread's next push does
not pass its held one.

* task_queue: cancel by ident and find safely from other threads

task_queue_cancel_task() and task_queue_find() from another thread met
the same switch and the same unlocked lists as a push did.
task_queue_cancel_ident() cancels by ident from any thread: a locked
walk when the threaded queue is up with nothing held, otherwise an ident
left for the next gather, which applies it after its pushes, to a held
push as well. A held push cancelled so retires cancelled without
running, and its callback runs. A find from another thread goes through
the switch's gate and reports false when it cannot walk the lists. The
queue's own workers and GCD threads skip the gate, and a find on the
main thread also sees held pushes. Each worker keeps its thread id in a
slot of its own and clears it before it reports that it left, so a
thread that reuses a joined worker's id is not taken for one. The switch
test cancels and finds from other threads on both queues and across a
put-off switch, and holds the main thread in the gather to show that a
thread's cancel of its own held push is not lost. Once extra workers
start, finds from handlers on the one worker and on an extra still skip
the gate, and a find with a joined extra worker's id from inside deinit
is refused.

* autoconfig: make connects reported on other threads on the main thread

libusb's hotplug thread and the Wii's HID poll thread report pads from
their own threads, where task_queue_find() can no longer answer exactly:
at a switch, or on the unthreaded queue, it reports false. Off the main
thread, input_autoconfigure_connect_ex() and
input_autoconfigure_disconnect() now copy the request into a main-thread
task and return true. Its callback makes the call on the main thread, in
the order the requests were made, so the duplicate check, the port's
sequence number and the settings it reads are the main thread's. A new
sample drives task_autodetect.c on the task queue with the rest stubbed,
reports pads from another thread, and checks that every find runs on the
main thread, that a pad reported twice queues one connect, that a
disconnect handed off is made and that a cancelled request is not.

* task_queue: wait, reset and deinit see pushes held for the main thread

On the unthreaded queue every push from another thread is held for the
next gather. A wait with nothing running returned without running it,
and a reset, the exit drain's included, left it to run normally
afterwards. Cancels held for the gather were never freed at deinit.

The unthreaded wait now drains held pushes first, and a reset drains
them into the runner before it cancels, so a held task meets a queued
one's fate. Deinit applies and frees held cancels; held pushes stay held
for the next runner's first gather, so no callback runs inside deinit.

* task_queue: make the Threaded Tasks flag atomic

Task handlers on the worker read it through task_queue_is_threaded(),
and the main thread writes it when the setting changes, so changing the
setting while a task ran was a data race.  ThreadSanitizer shows it on a
database scan whose queue is switched under it.

* task_queue: a parallel task names the extra workers it needs

retro_task_t gains parallel_workers, which task_init() clears. A
parallel push starts the extra workers it finds missing, up to the
larger of task_queue_set_parallel_workers() and the task's number, at
most 8. They serve every parallel task and stay until deinit, as before,
so the pool only grows; 0 keeps the frontend's number. The started count
is atomic, so a push that finds enough takes no lock, and one that needs
more claims the next entries under running_lock.

The parallel test has a task asking for three, a later one asking for
fewer, one asking for more than eight, a plain task whose number is
ignored, the frontend's number holding for a task asking for fewer, and
a task that leaves the number alone.

* task_queue: let deinit leave extra workers behind in detachable handlers

A parallel task may now also be detachable, and then runs on the extra
workers like any other parallel task. Deinit gives every worker the one
bound it gave the one worker, the extra workers first. One still inside
a detachable handler past it is left behind to finish alone, as the one
worker was, and one inside any other handler is still waited for. The
primitives of an init are held by deinit and by each worker it leaves
behind, and the last of them to let go frees them, so the one worker and
several extras can be left behind together.

A worker left behind, the one or an extra, now runs the task's cleanup
once the handler returns, then frees the task, so a detachable task
releases what it holds however it ends. Its callback still never runs;
its cleanup, like its handler, may touch only what the task owns.

The parallel test runs a detachable parallel task on an extra worker,
leaves one stuck past the bound, waits for a slow one that is not
detachable, and leaves the one worker and two extras behind together
with a new queue running beside them. Each task left behind is cleaned
up once, by its own worker, after its handler returns, and one not left
behind at its retirement as before. The switch test switches to the
unthreaded queue and back with extra workers live.

* task_queue: on Emscripten, sleep after a pass on the one worker only

On Emscripten every worker sleeps 1 ms after each handler call, a
pthreads workaround from when the one worker ran every task.  The extra
workers run parallel tasks, which work in many short passes, and the
sleep cost each of those passes 1 ms, more than the pass itself. The
extra workers no longer sleep; the one worker still does.

* emscripten: ask for at most 4 extra workers, and start 5 threads

In the browser a pthread the pool has not started is made on demand by
the main thread, and with the JS file system, where every thread's file
calls go through the main thread, 8 extra workers made each of those
calls about 40% dearer than 4 did.  The frontend asked for cores - 3
extra workers, up to 8; on Emscripten it now asks for at most 4, and the
build starts 5 pthreads up front, enough for the one worker and those 4.

* scan: read small plain files on parallel reader tasks, in list order

A database scan on the threaded queue pushes min(4, cores) reader tasks,
none below 2, as parallel tasks that ask for that many extra workers, in
HAVE_THREADS builds with lock-free atomics, except on Wii U, whose C
library and FAT layer take no locks, and on Vita, where Vita3K's sceIo
tables take none.  With Threaded Tasks off none starts and the scan
reads every file itself, stepped as before.

The scan hands the plain files and archives it would CRC whole to the
readers, up to 64 per reader ahead of it, each with its own copy of the
path.  A reader claims them in list order, opens and sizes each, leaves
one over 128 MiB to the scan unread and CRCs the rest a quantum per
call.  The scan takes each result at its own place in the content list,
so the databases see files in the same order as before, parks while the
next one is still being read, and reads itself whatever a reader could
not.

The scan never waits for its readers.  At its end, on cancel and once
the queue is unthreaded, each reader leaves at its next call and hands
back the file in hand.  Each reader drops its reference to what they
share in its cleanup, and the last reference frees it.  A queue deinit
leaves a reader stuck in a read behind after its 1 s bound and never
calls it again; the worker left with it runs its cleanup once the read
returns, which hands back the file in hand and drops the reference.

The scan test covers list order, files over the cutoff, failed pushes,
cancels and quits with a reader stuck in a read, and queue switches
under a scan; CI runs it under TSan, without threads and with no
readers.

* scan: run the command-line scan with no menu on the threaded queue

A command-line scan with no menu waits for the task queue to run dry,
and only a scan on the threaded queue gets readers.  In threaded builds
that run now uses the threaded queue whatever Threaded Tasks says; the
setting is not changed.

* menu: Import Content offers Scan Directory and Scan File

Both entries sit beside Content Scan when the database is built in.
OK on a file in Scan File's browser scans that file, as the Scan
action does.  The load_content_filter harness checks that the
entries are listed and that OK on a file starts its scan.

* manual scan: Content Location holds a full file path

The location can now be a file, so it is kept in a PATH_MAX_LENGTH
buffer, as is the scan task's copy.  A file whose path does not fit
is refused in the browser instead of being joined short.  The
load_content_filter harness sets a location deeper than a folder
buffer and checks that it is kept whole.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-26 13:28:42 +01:00
LibretroAdmin 5e40b656e8 rslc: print MSL prelude helpers as always_inline
The helpers rslc prints for GLSL builtins MSL lacks (mod, inverse,
matrixCompMult, textureSize, findLSB/findMSB) are printed static inline
always_inline like the shader's own functions, so under Metal's fast
math they round as the same code inlined into main0 would.
2026-10-26 12:15:10 +01:00
Davey Hughes 31b3c3736f rslc: Metal fast-math fixes (pow as powr, shader functions always_inline) (#19919)
* rslc: print pow as powr in MSL unless the base is a constant >= 0

The Metal driver compiles with fast math, where pow() of a negative
base returns pow(|x|, y). GLSL leaves a negative base undefined and
desktop GPUs return NaN, which is written as 0; SPIRV-Cross prints
powr() for that reason. The handheld colour-mod presets push a
channel slightly below zero, so on metal rslc turned it into colour:
a red bar came out 255,62,45 where glslang gives 255,0,0.

A constant base known to be >= 0 keeps pow(): the result is the same
and fast-math powr() is less exact there, which garbles
misc/print-resolution's text under SPIRV-Cross.

* rslc: print MSL shader functions as always_inline

The Metal driver compiles with fast math, where a function main0
calls can round a few ulp apart from the same code inlined into it.
SPIRV-Cross prints the shader's functions static inline
always_inline and rslc did not, so wherever a value sat on an 8-bit
rounding edge the pictures differed: xbr-lv3, misc/geom, hq2x and
others, and after the powr change blurs/sharpsmoother and
crt-royale-pal-r57shell.

Printed the same way, they match: on metal, 1128 of the 1166
non-bezel presets now give glslang's picture byte for byte, up from
989, and none that did stops.
2026-10-26 12:10:23 +01:00
libretroadmin ccd885c4f2 Temporal Upscaler: DLSS Neural Rendering
DLSS 5's Neural Rendering model runs over DLSS Super Resolution's
output when its library, nvngx_dlssnr.dll, is in the DLSS directory.
It goes through the same NGX session as Super Resolution: the driver's
NGX core loads the library from the directory it is given at init,
creates the feature (18) on the frame's command buffer and evaluates
it on Super Resolution's output with the core's depth and motion
vectors at render size. Cores before its driver (616.64) have no such
capability; the session then marks it unavailable and Super
Resolution goes on alone.

Settings > Video > Scaling > DLSS Neural Rendering holds the switch,
off by default, and the model's tuning: preset, style, intensity,
local structure, local tone, skin structure and auto skin mask. The
entry is shown only with DLSS selected, the library found, NGX not
OptiScaler's, and no session having found the driver without it.
The settings reach the backends as one word. The model reads its
tuning only at creation, so a changed setting rebuilds the feature
once it has held for 30 frames; turning it off retires the feature
through the same ring as Super Resolution's. System Information and
the log name the library's version, read from its file without
loading it, or say the driver needs updating.

The upscaler samples drive both backends against stand-in NGX cores
that know the feature: the tuning at creation, the evaluate's inputs
and guide sizes, the rebuild after a change holds, the release when
off, and a core without it; the loader sample finds the library and
reads its version.
2026-10-26 11:11:00 +01:00
LibretroAdmin 544fb6cde8 video_thread: hand the one GL context to the thread waiting for it
With one GL context handed between the core's thread and the video
thread, the owner word now also carries the waiting thread's ask and a
grant: a release goes to the thread that asked rather than back to the
free pool, so a take waits out exactly one hold of the other thread.
The push hands the context to the video thread together with the frame
it queues, whose pass takes the grant, and takes it back with a plain
take once that pass is done; there is no turn count or deadline.

gl_context_drain.py now runs the shipping take, release, drain and
command round trip as two coroutines over every schedule within three
preemptions - push, drain, command, free with and without the context
held, a video thread with back-to-back work - and fails on a deadlock,
on a bind while the context is current on the other thread, on GL work
without it, and on a take from the free pool past the other's ask.
2026-10-26 09:45:52 +01:00
Codex 3b37e81d9d vulkan test: include the shared fence submission helper
Extract the shipping helper alongside the import functions that call it. Track its source as a dependency so the harness rebuilds when the helper changes.
2026-10-26 08:57:33 +01:00
Codex 91b5ac8c9b vulkan test: validate the empty fence-signal batch
The fence helper submits one empty VkSubmitInfo. Check its fields instead of requiring a zero-batch submission.
2026-10-26 08:39:46 +01:00
Codex aa69f5d539 audio: preserve the worker exit acknowledgement
Keep the terminal acknowledgement distinct from a parked stop and clear only the latter. A late stop cannot erase the final notification and wait on a worker that has exited. Cover that interleaving deterministically in the handshake harness.
2026-10-26 08:39:46 +01:00
libretroadmin 16840f0349 task_queue_park_test: ask for the wake inside the pass before the push
The lane set wake_inside after task_queue_push() had returned, so a
worker that ran the first pass before then parked the task for its
whole horizon with no wake, and the lane timed out. Since workers are
woken by broadcast they take a task sooner and it failed about one run
in seven.
2026-10-26 08:13:06 +01:00
libretroadmin b47f5820bb Temporal Upscaler: load OptiScaler from the DLSS directory
OptiScaler had to stand beside retroarch.exe under a system DLL's name
(winmm.dll) to hook NGX, which put it in every session whatever the
core. As OptiScaler.dll in the DLSS directory it is now loaded only
when a core asks for the upscaler with DLSS set, just before the NGX
core library it hooks; the status names it without loading it.
2026-10-26 08:12:58 +01:00
libretroadmin 1e6e99cd07 REmove some more memsets 2026-10-26 08:12:15 +01:00
libretroadmin d032aaa2ad Avoid memset 2026-10-26 08:05:44 +01:00
libretroadmin 63dae1fe01 docs: install OptiScaler as winmm.dll
Next to retroarch.exe as nvngx.dll OptiScaler proxies NGX to itself; as winmm.dll it loads at start and proxies NVIDIA's own NGX library, which is how it runs DLSS on Vulkan.
2026-10-26 07:34:03 +01:00
libretroadmin f098d4e309 NGX: the snippet version's patch is a whole byte
NVSDK_NGX_GetSnippetVersion's low byte is the patch: 0x01360910 is
310.9.16, which is the DLSS 310.9.16 runtime's own file version, not
310.9.1.
2026-10-26 07:34:00 +01:00
libretroadmin 6bcaa97949 Temporal Upscaler: DLSS on Vulkan through OptiScaler
NVSDK_NGX_VULKAN_RequiredExtensions was required for Vulkan, but it is
deprecated and OptiScaler has only the feature requirement calls that
replaced it, so with OptiScaler the Vulkan driver had no NGX library
and never made the session. Either is enough now: the extensions come
from the old call where the library has it, else from
NVSDK_NGX_VULKAN_GetFeatureInstanceExtensionRequirements and
GetFeatureDeviceExtensionRequirements for DLSS.

OptiScaler itself is not asked: it forwards those calls to the NGX
library it proxies, which as nvngx.dll beside the executable is itself,
until the stack overflows. It adds NGX's extensions in its own
vkCreateInstance and vkCreateDevice hooks. The test's stand-in NGX has
OptiScaler's exports.
2026-10-26 07:33:57 +01:00
libretroadmin 86ada29b31 Vulkan: signal fences with an empty batch, not a NULL one
A fence-only vkQueueSubmit with a NULL pSubmits is valid, but
OptiScaler, loaded as nvngx.dll for the temporal upscaler, hooks
vkQueueSubmit and refuses a NULL pSubmits without submitting. The
fence never signals, and the hardware ring's wait for it hung every
core on its first frame. vulkan_queue_signal_fence() submits one empty
batch instead, and tools/vulkan_null_submit_check.c keeps the NULL
form out of gfx/.
2026-10-26 07:33:53 +01:00
Codex 361e816382 tls tests: link eventcounts with threaded VFS users
Include the eventcount implementation in the thread, stall and verification-hook source groups used by the Windows VFS read-ahead worker.
2026-10-26 07:08:12 +01:00
Codex 67dda089a4 tasks tests: link eventcounts with the threaded VFS
The Windows VFS read-ahead worker uses eventcounts. Include their implementation in the threaded cloud-sync and BSV replay test links.
2026-10-26 07:08:12 +01:00
Codex 6031e30543 Subtitles: retain applicable language from MP4 tracks
Decode container language codes and take an owned span before input-window retirement. Share ISO639 lookup with Matroska and cover extended-tag precedence and legacy numeric codes through the task loader.
2026-10-26 07:08:12 +01:00
Codex 63ef564d66 MP4: expose borrowed raw track language metadata
Retain mdhd language fields and supported elng values without changing public track layout. Keep precedence and malformed-input behavior covered by a portable sample.
2026-10-26 07:08:12 +01:00
Codex 17a93cf96c ci_local: verify the pinned host Emscripten SDK
Check the effective compiler environment and exact version before running SDK jobs locally. Cover missing, failed and mismatched tools in the runner self-test.
2026-10-26 07:08:12 +01:00
Codex 5c494883c9 Make subtitle read instrumentation portable across linkers
Rename the harness stream implementation at compile time and verify that actual reads are counted.
2026-10-26 07:08:12 +01:00
Codex 5356868fe5 Ignore Matroska language padding before applying size limits
Bound the semantic language value while honoring EBML string termination.
2026-10-26 07:08:12 +01:00
Codex 7178bb8f4f Bind Matroska subtitle language before releasing input
Decode terminated container strings and convert legacy ISO639 aliases only at the source boundary. Preserve explicit BCP47 text and its precedence.
2026-10-26 07:08:11 +01:00
Codex b2036d5577 Retain applicable subtitle language with each track
Prepare its comparison key within the parsing work budget and preserve the raw language through track retirement.
2026-10-26 07:08:11 +01:00
Codex 7d62312c6f Retain raw WebM track language metadata
Expose borrowed language spans without changing the public track layout. Prefer BCP47 metadata regardless of field order and retain the Matroska legacy default when the field is absent. Cover empty overrides, raw padding, long values, partial input and cursor lifetime.
2026-10-26 07:08:11 +01:00
Codex d6155abbd7 Prepare immutable WebVTT cue language keys
Normalize each distinct language annotation while sealing the cue tree. Retain raw annotations and inherited-language indices, share changed comparison keys, and release preparation state before publishing the track. Cover cancellation, allocation failures, repeated keys and chunk boundaries.
2026-10-26 07:08:11 +01:00
Codex b3c9ede0fb Prepare normalized WebVTT language comparison keys
Normalize aliases and extended ranges incrementally while preserving raw annotations and repeated constraints. Transfer changed result buffers into prepared owners without another copy.
2026-10-26 07:08:11 +01:00
Codex 394d4551a6 Validate WebVTT language-tag grammar incrementally
Check BCP47 tag structure, duplicate variants and extension singletons with bounded incremental work. Preserve well-formed unknown tags and recognized grandfathered forms.
2026-10-26 07:08:11 +01:00
Codex 900a6d4383 Bound WebVTT language alias lookups
Search typed IANA aliases one comparison at a time and return borrowed static spans. Track the generated header in each consuming sample build.
2026-10-26 07:08:11 +01:00
Codex e29976d0e1 Prepare WebVTT language alias data
Generate compact typed aliases from a pinned IANA snapshot, retaining grandfathered tags without replacements. Verify pooled data in the standalone subtitle suite.
2026-10-26 07:08:11 +01:00
Codex 72eaefcf8e Prepare WebVTT language matching data
Prepare bounded language-range filtering and enclosing-language indices during loading. Preserve raw annotations and borrowed fallbacks for immutable queries.
2026-10-26 07:08:11 +01:00
Codex 7a3966ee3a Prepare incremental WebVTT cue and stylesheet data
Retain owned cue trees, regions, STYLE components and bounded selector predicates for the builtin subtitle pipeline. Selector grammar, cascade, layout and styled rendering remain unfinished; this is an unpublished integration checkpoint.
2026-10-26 07:08:11 +01:00
LibretroAdmin 769877ef88 winraw: drop two unused locals in winraw_input_state 2026-10-26 06:46:16 +01:00
libretroadmin e5241e5cf7 docs: setting up DLSS, FSR and XeSS
An end user's guide to the temporal upscaler: where to get each vendor's library, where it goes, the settings, the status line and OptiScaler.
2026-10-26 06:41:58 +01:00
libretroadmin 4284e04654 Temporal Upscaler: load the upscaler only for a core that asks for it
The upscaler's libraries were loaded, and the Vulkan instance and
device made for them, whenever the setting was on, for every core.
OptiScaler standing in for nvngx.dll hooks the whole process once
loaded, and its Vulkan hooks hang or crash cores that never use the
upscaler.

A core now asks with RETRO_ENVIRONMENT_GET_HW_RENDER_UPSCALER_INTERFACE
during retro_load_game, beside SET_HW_RENDER; only then are the
libraries loaded and the context made for them, until the core
unloads. The System Information status finds the NGX library and
tells OptiScaler from its version resource without loading it.
2026-10-26 06:41:54 +01:00
libretroadmin 0a22904483 NGX: read the DLSS runtime's version from its file
NVSDK_NGX_GetSnippetVersion packs the patch and build into a nibble
each, so 310.9.1 read as 310.9.16. Take the file version on Windows
and the version in the file name on Linux, and decode the snippet word
properly where neither says.
2026-10-26 06:41:51 +01:00
LibretroAdmin d0490c9c1f winraw: drop RETROARCH_RAWINPUT_POLL and the window-thread path
Keyboard, mouse and controllers are always read in bulk by the poll,
on a window the first poll makes on the polling thread. The switch
only kept the earlier path, with its window made by init and its
reports taken by that thread's pump, and the =2 sink registration, for
comparison; both go, with the pump's staleness measurement that only
that path used.
2026-10-26 06:26:19 +01:00
Codex c6a81d0348 wiiu test: use the relocated SDK headers 2026-10-26 05:51:38 +01:00
Codex d40c654fec salamander test: follow the selected directory entry
Check pending-core fallback independently of filesystem enumeration order, and clean both possible core files.
2026-10-26 05:51:38 +01:00
Codex bb0e58ddba audio test: compare fill caps on valid display schedules
Reject missed display deadlines in either baseline before comparing queue depth. Keep the reduction and underrun checks, with bounded retries and an explicit failure when no valid schedule is observed.
2026-10-26 05:51:38 +01:00
LibretroAdmin 96418077d9 Drop the RETROARCH_INPUT_SNAPSHOT and RETROARCH_VULKAN_RESIZE_LATE switches
Both only selected the previous behaviour for comparison and no test
or workflow sets either: the snapshot bridge is always used for the
drivers that provide it, and Vulkan always applies a pending resize
before drawing the frame.
2026-10-26 05:47:46 +01:00
LibretroAdmin 284c5655dc Vulkan: resolve RETROARCH_VULKAN_RESIZE_LATE at init; QNX: unset ORIENTATION
vulkan_init reads RETROARCH_VULKAN_RESIZE_LATE once into
VK_FLAG_RESIZE_LATE, so vulkan_frame tests a bit of vk->flags.

qnx_ctx takes an unset ORIENTATION as 0 instead of passing NULL to
atoi.
2026-10-26 05:39:36 +01:00
libretroadmin a3e0639a15 Temporal Upscaler: the DLSS runtime's version, OptiScaler, presets A to O and sharpness
The DLSS feature library says its version through its own export
(NVSDK_NGX_GetSnippetVersion), which the loader now reads from the DLSS
directory before NGX loads the file; System Information and the log
name it. OptiScaler standing in for NGX is known two ways: its module's
version resource names it at the probe, and every parameter table it
hands out carries its key, which the session checks after init. The
status says "via OptiScaler".

The preset setting carries every letter NGX numbers, A to O, in NGX's
own numbering, so a newer runtime's presets can be asked for without a
new build; a letter older than the runtime found is marked as not in
it, and one the runtime does not know falls back to its default. A
sharpness setting (percent) rides in the mode word: FSR sharpens with
its own pass, DLSS 2.5.0 and older and OptiScaler's RCAS take the NGX
Sharpness parameter, XeSS and newer DLSS ignore it. A sharpness change
alone makes no new feature.

The loader samples read the version from a stand-in feature library
and see OptiScaler's key and, under Wine, its product name; the
upscaler samples check the status, the sharpness reaching each
backend and the feature kept across a sharpness change.
2026-10-26 05:25:19 +01:00
LibretroAdmin 05ae096772 Wii U: whole-file reads in place, behind HAVE_WIIU_DESCRIPTOR_IO
Built with HAVE_WIIU_DESCRIPTOR_IO=1 (Makefile.wiiu, off by default),
Wii U takes the descriptor path for whole-file reads, and since its SD
reads land by DMA in whole cache lines, vfs.h gives it
VFS_DESCRIPTOR_READ_ALIGN 64. filestream_read_file() then allocates its
buffer on a line with a line spare for the terminator and asks for whole
lines, so the file lands in place in one read() and one FSReadFile,
with no bounce copy.

samples/frontend/wiiu_descriptor_read runs filestream_read_file() with
that alignment over a stand-in read() that checks each request and
loses the rest of every line it covers, as a DMA read does.
2026-10-26 05:22:29 +01:00
libretroadmin ecb4fa5a59 vfs: read ahead of sequential reads through a Win32 mapping
Windows starts no read-ahead on a mapped view, so every first touch of
a page stalls the reading thread on the disk; with a disc image read
from the core's frame this shows up as a periodic core-time spike that
a warm file cache makes go away. Mapped reads that follow on from the
last one now hint the next 4 MiB each time half of it is used.

The hint is PrefetchVirtualMemory where it exists (Windows 8 on). Below
that a per-stream thread touches the view ahead of the reader, which
brings the pages in without copying anything; it is lock-free (atomics
and an eventcount) and only exists under HAVE_THREADS.
RETRO_VFS_PREFETCH=touch|none selects it or neither, for tests. Layers
that keep local handles behind an installed VFS (vfs_hybrid) now pass
filestream_prefetch() on through filestream_set_prefetch_cb().
2026-10-26 05:12:23 +01:00
LibretroAdmin d97736342a Wii U: the runtime moves to os/wiiu
The tree's Wii U layer (the coreinit, GX2, AX and friends headers, the
RPX import stubs, startup and linker scripts, the newlib hooks, the
exception handler, the SD devoptab, the HBL loader and the elf2rpl and
slang-cafe host tools) lives under os/wiiu, beside os/gekko. Makefile.wiiu
builds from there with -Ios/wiiu -Ios/wiiu/include, so <wiiu/...> headers
resolve as before; the few includes that reached files through the
tree root name os/wiiu.
2026-10-26 05:01:59 +01:00
LibretroAdmin 130bc488d0 Wii U: newlib lock hooks in the shape newlib 4.2 calls
newlib before 4.3 calls four lock hooks, with init told whether the lock
is recursive and every hook returning an int, and the Wii U toolchain's
newlib is 4.2. newlib_locks.c provides that set there and 4.3's separate
recursive hooks after it, chosen from __NEWLIB__/__NEWLIB_MINOR__ as
os/gekko/kernel/newlib.c does. samples/frontend/wiiu_newlib_locks runs
its checks against both.
2026-10-26 05:01:56 +01:00
LibretroAdmin 0f9f073cdb vfs: Wii U reads whole files through stdio again
Whole-file reads on Wii U went to open()/read() on the SD devoptab
without that path having run on hardware, and they are the first thing
a boot does: the salamander config and retroarch.cfg. Wii U leaves the
descriptor-I/O list until its descriptor reads have been exercised, so
it reads through the aligned stdio buffer as it did before.
2026-10-26 05:01:52 +01:00
LibretroAdmin 20ce60a62d Wii U: an SD read never writes past what it was asked for
FS reads land by DMA and the cache lines they cover are invalidated
whole, so a read into the caller's buffer that ends partway through a
line loses whatever the CPU last wrote to the rest of it, such as the
next heap block's header. sd_fat_read_r now reads in place only the
whole 64-byte lines of a line-aligned buffer and takes the rest through
its aligned bounce buffer, copying only the bytes that arrived.

Read, write and seek failures return -1 with errno set rather than 0 or
a raw FS status; a seek outside the file or with a bad origin leaves the
position alone and releases the device mutex; stat decides whether the
path is the mount root before freeing it.

samples/frontend/wiiu_sd_devoptab runs the devoptab against a stand-in
FS that invalidates whole lines, with stand-ins for devkitPPC's
<sys/iosupport.h>.
2026-10-26 05:01:48 +01:00
LibretroAdmin 8ea21b8cbf salamander: a core switched to is marked like one launched
The frontend driver state keeps the fork mode the platform last
accepted (frontend_driver_get_fork()), and config_save_file_salamander()
marks the core it saves as pending when the process is leaving for it,
as salamander does before it launches a core. A core that a running
RetroArch switches to and that never comes up is passed over at the
first boot after; a plain quit or "Save Configuration" leaves no mark.

samples/frontend/salamander_pending takes the frontend's salamander
config functions out of configuration.c and checks both the switch and
the plain save.
2026-10-26 05:01:44 +01:00
LibretroAdmin a025681d84 salamander: link only what the launchers use
No launcher calls into lrc_hash.c, and on x86 its SHA dispatch reaches
cpu_features_get(), which no launcher links: the DOS salamander, whose
link keeps every section, cannot resolve it. lrc_hash.o leaves every
salamander object list. The Wii launcher's logger build links
net_socket.c, which reads cpu_features_get_time_usec(), so that build
links features_cpu.o as well.

compile-matrix.sh now links each launcher's libretro-common list, read
from its Makefile, with frontend_salamander.c and a host frontend
driver (tools/platform_stubs/salamander_frontend.c), so a unit in a
list that needs one outside it fails there.
2026-10-26 04:21:05 +01:00
libretroadmin 7dd5186ef0 Drop the libretro-common standalone patch applied at the tree root
b6296db2 was the standalone libretro-common repository's copy of the
FidelityFX API and XeSS loaders, applied here instead: it put a second
copy of gfx/amd, gfx/intel, their headers and samples at the root of the
tree, beside the real ones under libretro-common/, which nothing builds.
Remove them; the patch belongs to libretro/libretro-common.
2026-10-26 04:07:13 +01:00
libretroadmin b6296db205 gfx/amd, gfx/intel: the FidelityFX API and XeSS loaders
Run-time loaders for AMD FSR through the FidelityFX API and Intel XeSS,
C89, beside the NGX one: the SDKs' descriptor and parameter layouts and
their type numbers declared here so no SDK header is needed, the
library looked for under the SDKs' file names in a directory of the
caller's and kept once found. The samples pin the layouts to the SDKs'
sizes and offsets on x86-64 and drive the loaders against stand-in
libraries that export the SDKs' entry points, on Linux and under
mingw/Wine.
2026-10-26 04:05:58 +01:00
libretroadmin aca5868b04 Temporal Upscaler: FSR and XeSS beside DLSS
The setting gains AMD FSR and Intel XeSS, on the Vulkan and Direct3D 12
drivers as DLSS is. Each runs on its vendor's library, loaded at run
time from a directory of the user's and never bundled: FSR through the
FidelityFX API (amd_fidelityfx_vk.dll for Vulkan, FSR 3.1; SDK 2's
amd_fidelityfx_loader_dx12.dll or SDK 1.1's amd_fidelityfx_dx12.dll for
Direct3D 12, where the newest version the library offers the device is
taken, FSR 4 on the GPUs that have it), XeSS through libxess.dll on
either driver. The FSR and XeSS directories are settings beside the
DLSS one, upscalers/fsr and upscalers/xess in the RetroArch directory
by default.

libretro-common gains the loaders (gfx/amd/ffx_loader, gfx/intel/
xess_loader), C89, with the SDKs' descriptor layouts and type numbers
declared so no SDK header is needed, and samples that pin those layouts
to the SDKs' sizes and offsets and drive the loaders against stand-in
libraries. The drivers go through one dispatch (vulkan_upscaler,
d3d12_upscaler) over the three backends, which share the output image,
the layout moves around a dispatch and the ring that retires an image
and context after the frames in flight. XeSS builds its pipelines in the
library's background thread and the frames are shown as they are until
they are ready; nothing on the frame path waits. The frontend's mode
word carries the quality and preset as set and each backend translates
it, so the DLSS preset keeps its meaning and the others ignore it.

The upscaler samples run FSR and XeSS behind the dispatch on lavapipe
and on Wine's D3D12 against the stand-ins, with the inputs' flags,
formats, states, scales and reset read back from the descriptors the
libraries were given.
2026-10-26 04:05:55 +01:00
LibretroAdmin b974d0a885 salamander: pass over a core that did not start
Salamander marks the core it launches in its config
(libretro_path_pending) and the frontend clears the mark once its
drivers are first up. A boot that still finds the mark on the
configured core launches the first other core in the cores directory
instead, so one core that dies on startup no longer takes every later
boot down with it; with no other core, nothing is launched and both
entries are dropped so the next boot looks again. A passed-over core
never falls back to salamander itself.

samples/frontend/salamander_pending boots salamander against a cores
directory with a stand-in frontend driver and checks the mark, the
pass-over, and the empty fallback.
2026-10-26 04:03:49 +01:00
LibretroAdmin 06a85bc900 Wii U: newlib locks on Cafe OS mutexes
devkitPPC's newlib serialises stdio, the environment, the time zone
and the atexit lists only through the weak __syscall_lock_* hooks,
which nothing on Wii U defined, so with threads on, libc state shared
between the frontend's threads went unlocked. wiiu/system/newlib_locks.c
provides them on OSMutex: newlib's lock word starts at 0, a mutex is
created on first use and published into it by compare-and-swap, and
OSMutex being recursive serves the recursive locks as well.

samples/frontend/wiiu_newlib_locks runs the hooks against stand-in
mutexes: threads racing first use leave one mutex published and the
counter it guards exact, plus the recursive, try and close paths.
2026-10-26 03:46:36 +01:00
LibretroAdmin 412d2c4224 Wii U: the exception handler covers every core
The DSI, ISI and program handlers are installed with
OSSetExceptionCallbackEx in global all-cores mode, so a fault on a
thread running on core 0 or 2 prints the register dump and stack trace
instead of falling through to the system's fatal error screen. The
first core to fault owns the report; a second one parks.

samples/frontend/wiiu_exception_cores checks the three handlers are
installed for all cores and that a second fault parks instead of
reporting into the same buffer.
2026-10-26 03:46:33 +01:00
LibretroAdmin 3a9c20fcf4 rslc, d3d10: answer three MSVC 2022 warnings
The pointer-array growth in the rslc frontend takes what its function
returns on failure, so a bool function returns false rather than NULL;
a matrix-compmult fold compares unsigned with unsigned; d3d10's frame
loses an unused counter.
2026-10-26 03:44:18 +01:00
LibretroAdmin 184f65aa03 menu: image picks keep their OK action in the media viewer (#19911)
The fullscreen viewer opens a file-list row only when it is not a
FILE_TYPE_IMAGE pick, so choosing a Background image in XMB sets the
wallpaper again instead of opening the image. The same rule gates OK in
the viewer on XMB, Ozone, MaterialUI and RGUI.

The menu media harness gains --wallpaper-pick, which goes through the
Background setting and its browser with the real driver and callbacks;
it runs for XMB and MaterialUI, the menus that offer the setting.

Closes #19911
2026-10-26 03:41:37 +01:00
LibretroAdmin 920bd8e359 metal: load only the LUTs a pass reads
A preset's LUT images are decoded only when some pass samples them or
takes their size, as on the other slang drivers.
2026-10-26 02:19:12 +01:00
Codex 9723d56005 Intl: keep internal label rows out of translation conversion
Restore the macro include overwritten by the Crowdin round trip. Exclude both internal label headers from export, import and repacking, including path-qualified inputs and stale JSON files. Check this boundary in the packed-table workflow.
2026-10-26 02:12:24 +01:00
Codex b7436d8ffc Netplay: finish host setup when relay lookup fails
Resume direct hosting after an empty relay response or timeout instead of deferring another lookup. Cover failure, retry, success, deadline and cancellation through the shipping decision functions.
2026-10-26 02:05:24 +01:00
LibretroAdmin 17dc7f898b d3d10/d3d11/d3d12: load only the LUTs a pass reads
A preset's LUT images are decoded once every pass is built, and only
those some pass samples or takes the size of. The deferred load no
longer decodes them up front: the install loads them for the chain.
2026-10-26 01:40:31 +01:00
github-actions 1704e7b6d0 Fetch translations from Crowdin 2026-10-26 00:23:58 +00:00
Codex fdc75da12f Tests: link WebM codecs with MP4 disabled
WebM playback uses H.264 and HEVC independently of the MP4 demuxer. Keep their decoder objects in the common player harness sources so the MP4=0 playback and AV1 handoff checks link.
2026-10-25 21:09:02 +01:00
Codex 3ea10fd65a Tests: verify Xvfb and Openbox readiness before SDL startup
Require Openbox to manage a mapped client before launching SDL cases. Check that the Xvfb and Openbox processes remain alive so a different server or manager cannot satisfy their readiness checks.

Exercise pending management, cleanup and dead-process rejection with portable mocked tests.
2026-10-25 19:50:06 +01:00
Codex c2bc8557f1 FFmpeg: release replaced hardware device contexts
Keep the previous codec-owned reference until admission succeeds, then release it before installing the new context. Cover repeated replacement and rejected admission with a portable lifecycle regression.
2026-10-25 19:39:06 +01:00
Codex 2cc9d5bfb2 CI: select software Vulkan for CPU codec fixtures
Pin the optional FFmpeg CPU fixtures to Lavapipe and declare the Mesa dependency so driver discovery is consistent across runners.
2026-10-25 19:39:06 +01:00
Codex 81f865092f Check modal media audio through window restoration
Support capture-free native audio checks, require the requested audio driver, and compare underruns through modal exit and geometry restoration. Restore threaded windows on their owning thread and retain lifecycle diagnostics for failed image continuity runs.
2026-10-25 18:46:10 +01:00
Codex 35a22f1a6d Allow formats CI time for cold dependency setup
The ten-minute job deadline can expire during the AV1 parameter checks after dependency installation and earlier format tests. Allow thirty minutes while keeping every test and the required aggregate result.
2026-10-25 18:29:29 +01:00
Codex c6b7acd2ff Namespace NGX backend helpers in Griffin builds
Give the D3D12 and Vulkan static helpers distinct names so their unity build definitions do not collide.
2026-10-25 18:29:29 +01:00
Codex e8b84bea8b Keep menu mixer audio paced by the audio consumer
Supply menu filler from the threaded audio consumer so modal frame stalls do not starve the mixer. Preserve core handover cutoffs and partial writes, and use the existing idle path when the sink cannot progress.

Cover producer stalls, core resumption, suspension, partial writes and failed-sink recovery in the legacy and transport regression lanes.
2026-10-25 17:42:50 +01:00
libretroadmin 5c763e756e Temporal Upscaler: the DLSS render preset, and the mode named in the log
A Temporal Upscaler Preset setting picks the DLSS model: the runtime's
default, or the transformer presets K (the best image, at more GPU
time) and J (less ghosting, some flicker), the two the SDK still offers
an application. The mode is one word on the way to the backends, the
quality and the preset, and a change of either makes the feature
again; the preset is set as the SDK's hint for every quality. The log
names the mode and the preset when the feature is made.

The upscaler samples' fakes record the preset hint and the tests check
it; they also clear the runtime they plant, so a run after another
starts without it.
2026-10-25 17:40:43 +01:00
LibretroAdmin 53d015df0d vulkan: a scanline subframe keeps the rest of each pass's target
Under the rolling scanline simulation a subframe draws one band of a
pass's target; the render area is now that band and the target keeps
its contents around it, cleared once when it is new, where it was
discarded every subframe. A pass that can discard clears only its
band.
2026-10-25 17:32:16 +01:00
LibretroAdmin a90bebdeaa vulkan: clear the target of a pass that can discard
A pass whose fragment stage can discard draws to a target cleared to
transparent black, as glcore and D3D clear theirs, instead of one
whose contents are undefined. crt-royale's mask-resize passes, which
discard past their tiles while the next pass filters across the edge,
stop picking up whatever memory the target was given.
2026-10-25 17:03:57 +01:00
LibretroAdmin ca3f823755 Low-latency power plan: turn off USB 3 link power management
The copy now also sets USB 3 Link Power Management to off on AC, so
xHCI links stay out of U1/U2 and connected controllers answer without
a link wake.  Like the other device settings it is best effort: a
system without the setting applies the plan without it.

The win32_power_plan harness checks the value on the copy, that the
user's own plan keeps its setting, and the missing-setting case.
2026-10-25 16:02:52 +01:00
LibretroAdmin e30cbe927b rslc: rename words GLSL 3.30 and ES 3.00 reserve
packed, row_major, attribute and varying are free in Vulkan GLSL and
reserved in the GLSL glcore is given, so the printers rename them as
they do the other reserved words; struct and block member names go
through the same renaming. scanline-classic's famicom-rgb and
color-base, which name a local and a parameter packed, compile on
glcore again.
2026-10-25 15:46:48 +01:00
libretroadmin e77e858e37 Temporal Upscaler: Shutdown1 as the NGX core library exports it
The core library's NVSDK_NGX_*_Shutdown1 takes a second argument the
SDK's prototype leaves out: an int the library writes, whether it is
still in use, which the SDK's static library hands it. Called with
one argument, the library wrote through a register, faulting the
close of every hardware core. The loader's prototype and every call
now pass it, and the sample fakes write it as the library does.
2026-10-25 14:50:13 +01:00
libretroadmin 8c31bb1692 Temporal Upscaler: what Init was given stays until after Shutdown
The feature info and path list NGX is initialised with were the init
function's locals, the wide path freed on return; the core library
reads them again at shutdown, where the close of a hardware core
faulted inside it. The session owns them until after Shutdown1, on
Vulkan and Direct3D 12 alike. The sample fakes read the path list
again at shutdown, as the library does, and the tests check it.
2026-10-25 14:37:23 +01:00
libretroadmin 10ccead0c2 Temporal Upscaler: the shutdown the SDK asks for, and the teardown logged
The close of a hardware core faulted inside the NGX core library in
NVSDK_NGX_VULKAN_Shutdown1 with the device passed. The SDK asks for
Shutdown1(NULL), every device's resources; the capability parameter
map is destroyed before it too, as the SDK asks. Each teardown step
is logged so a fault there names the call.
2026-10-25 14:31:12 +01:00
libretroadmin 5f76f0e2bf Temporal Upscaler samples: the runtime in the directory, and without it
Both upscaler samples plant the DLSS runtime's file in their test
directory before the session is expected to come up, and first check
that without it the driver library is never called.
2026-10-25 14:16:35 +01:00
libretroadmin dc4d0a0052 Temporal Upscaler: fix the crash on every hardware core
The NGX core library's NVSDK_NGX_VULKAN_Init_ProjectID takes no
proc-address getters; the form with them is Init_ProjectID_Ext.
Calling the short one with the long argument list made the driver
read the SDK version as a pointer and fault on any Vulkan hardware
core, DLSS runtime present or not. Resolve _Ext and use it, with the
short form as the fallback on libraries without it.

Neither backend touches the driver library now unless the DLSS
runtime is in the DLSS directory; without it the upscaler logs and
stays off.
2026-10-25 14:11:16 +01:00
Codex 9a13b897bb Cg: reuse GLES declarations in the shader backend
Use the Cg header switch to reuse the GL declarations already supplied by gl2_common. This prevents a desktop function declaration from colliding with the GLES3 symbol wrapper.

Cover GLES2 and GLES3 Cg combinations with Linux and MinGW compilers in the compile matrix.
2026-10-25 13:57:37 +01:00
libretroadmin eb946dbd5b Temporal Upscaler: the parameter object's own methods
NVSDK_NGX_Parameter is a C++ interface: the NGX core library exports
no NVSDK_NGX_Parameter_Set*/Get*, those are the SDK's static library
calling the object's virtual methods. Looking them up as exports found
nothing on a real driver (the log said so), so no NGX was ever usable.
The loader now calls the vtable, in the slot order the SDK's own static
libraries use: each overload group reversed on MSVC, declaration order
on the Itanium ABI, read from the libraries' disassembly. The stand-in
libraries of the tests give their parameters that vtable, in the
platform's order, so the tests call through the same path.
2026-10-25 13:45:28 +01:00
libretroadmin a86f11d165 Temporal Upscaler: the probe logged when the driver starts
With the setting on, the log said what the probe found only once a
hardware-rendered core attached; in the menu alone it said nothing.
Logged when the Vulkan and Direct3D 12 drivers start as well.
2026-10-25 13:27:52 +01:00
libretroadmin 5a57d7259f Temporal Upscaler: the setting always listed, the probe logged
The Temporal Upscaler setting was listed only once the NGX core library
and the DLSS runtime were both found, which left nothing to see and
nothing to read when either was missing. It is listed on the Vulkan and
Direct3D 12 drivers as the other scaling settings are, and when the
driver comes up with it on, the log says what the probe found: where
the NGX core library was looked for and loaded from, which entry points
it lacked, and whether the DLSS runtime is in the DLSS directory.
ngx_lib_status() carries that report out of the loader.
2026-10-25 13:25:10 +01:00
libretroadmin f93e81abc6 D3D12: COBJMACROS before the NGX backend's d3d12.h
In the MSVC griffin build the backend is the first to include d3d12.h,
without COBJMACROS, so the C method macros never existed for the files
after it and the link failed.
2026-10-25 13:16:40 +01:00
libretroadmin 096a0efaa9 Temporal Upscaler: the NGX core library's own entry points
The driver's NGX core library exports NVSDK_NGX_*_Init_ProjectID, with
the SDK version before the feature info, and NVSDK_NGX_*_EvaluateFeature;
Init_with_ProjectID and EvaluateFeature_C are the SDK's static
library's names for them. Resolving the latter found nothing, so no
NGX was ever found on a real driver and the Temporal Upscaler setting
never appeared. The library is also looked for by name on the library
search path, where the driver installs it. The stand-in libraries of
the tests take the library's shape.
2026-10-25 13:16:36 +01:00
LibretroAdmin fd0220d3b2 slang: load only the LUTs a pass reads, on Vulkan and glcore
A preset's LUTs are named up front for the semantic map and their
images loaded once every pass is reflected, only for those a pass
samples or takes the size of. A Mega Bezel screen-only preset stops
decoding and uploading 15 to 19 of its 23 images, about 90 MB.
2026-10-25 12:53:45 +01:00
Codex 122b74023f Win32: keep focus polling within the window resize minimum
Use core geometry for the minimum size in focus polling, matching WM_GETMINMAXINFO. Enforcing the initial window scale during a permitted resize can repeatedly enlarge the window and recreate presentation resources.

Check that focus polling preserves a window above the core minimum and still enlarges it when core geometry grows.
2026-10-25 12:50:28 +01:00
Codex 799e523fad CI: give VP9 and Matroska conformance a separate job
The combined formats job exceeded its ten-minute limit before finishing codec checks. Run the VP9 and Matroska checks in their own job with a twenty-minute budget, preserving every test and sanitizer setting.

Keep the original required check as an aggregate that succeeds only when both suites pass.
2026-10-25 12:44:28 +01:00
libretroadmin 98767d6024 Temporal Upscaler: the DLSS runtime in upscalers/dlss
The DLSS Directory's default moves from the System directory's dlss to
upscalers/dlss in the application directory, beside shaders and system,
where other upscalers' runtimes will sit too.
2026-10-25 12:39:47 +01:00
libretroadmin c93e833c8b D3D12: DLSS Super Resolution through NGX
The Direct3D 12 driver gets the temporal upscaler the Vulkan driver
has: a core that brought motion vectors and depth through the upscaler
interface has its frame evaluated by DLSS on the frame's command list,
and the output takes the core's texture's place for the copy into the
filter chain. gfx/common/d3d12_ngx.c mirrors vulkan_ngx.c: one session
per device, a feature per output size and mode made for the mode's
full dynamic range, inputs moved to shader-readable states and back,
the output a copy source, retired features and outputs released after
the frames in flight.

samples/gfx/d3d12_upscaler drives the real backend on Wine's D3D12
(vkd3d over lavapipe) against a stand-in NGX core library whose
evaluate copies the colour into the output, so the feature and evaluate
parameters, the states and the readable output are pinned on every
runner. Compile-matrix lanes build the backend and the driver with
HAVE_NGX, with and without threads.
2026-10-25 12:35:10 +01:00
Codex 6fc54858df media: yield between seek preparation phases
Separate audio preparation and final picture publication from video seek completion so independent expensive stages do not share one continuation. Preserve prepared PCM and cancellation ownership across the additional yields.
2026-10-25 11:46:35 +01:00
Codex bf72d8d9e3 video: test peer progress during pending seeks
Exercise a second player through EOF while a seek is waiting for input or continuing on the shared worker. Check replacement output and presentation slot retirement in inline and threaded modes.
2026-10-25 11:23:02 +01:00
Codex bf02c57dcb audio: retain mixer streams across core switches
Keep mixer voices while rebuilding the core audio pipeline. Reuse the initialized mixer at the same output rate; full teardown and output-rate changes still release it. Cover playing and stopped streams across repeated initialization in both pipeline modes.
2026-10-25 11:07:17 +01:00
LibretroAdmin 0575c56dca slang: feedback for a last pass with no scale
A last pass without scale_type that reads its own feedback, as Mega
Bezel's combine pass does, gets the render target and copy that a
viewport-scaled one gets, on Vulkan and glcore. Its feedback sampler
was left unwritten, which lavapipe crashes on and other drivers read
as garbage.
2026-10-25 11:03:12 +01:00
libretroadmin 42c4694858 Temporal Upscaler: sizes as packed dims
The output, render and range sizes the upscaler passes around are
VIDEO_SCALE_PACK words, as the rest of the video code carries them:
one field or argument per size in the Vulkan NGX session, the frontend
module's backend and the driver, in place of width and height pairs.
2026-10-25 10:55:41 +01:00
libretroadmin a9ba46af7a Temporal Upscaler: the core interface and DLSS on Vulkan
A hardware-rendered core now has a way to feed a temporal upscaler:
RETRO_ENVIRONMENT_GET_HW_RENDER_UPSCALER_INTERFACE hands it an
API-neutral interface with get_request, which says whether an upscaler
will take the frame and what render size it wants for the mode (so the
core renders smaller, jittered and without its own temporal AA), and
set_inputs, which brings the frame's depth, motion vectors, jitter and
optional exposure and transparency masks beside the color image the
existing render interfaces hand over, under the same lifetime and sync
rules. Images are the context API's own handles, so the same contract
serves Vulkan and Direct3D 12, and later FSR and XeSS. No existing
interface version changes; a core that never asks is unaffected.

The Vulkan driver runs DLSS Super Resolution over it through NGX
(gfx/common/vulkan_ngx.c): a session on the device, the mode's render
sizes from NGX's optimal-settings callback, a feature made for the
mode's full dynamic range and the output size and re-made only when
those change, the frame's size as the render subrect, the output image
handed to the filter chain in the core's place, inputs in a depth
layout moved for the evaluate and back. Retired features and images go
after the frames in flight; nothing waits. The context enables the
device and instance extensions NGX asks for, on a device the core makes
as well, only while the setting is on, which now reinitializes the
driver so they can be. A Quality setting picks the mode (Auto, Native,
Quality, Balanced, Performance, Ultra Performance).

samples/gfx/vulkan_upscaler pins the glue on lavapipe under the
validation layer against a stand-in NGX core library, built with gcc
and mingw in the samples-gfx workflow.
2026-10-25 10:43:54 +01:00
Codex f7d82e4c0e media: decode AVC and HEVC tracks in Matroska
Retain container timing and hidden-picture state through incremental reads, reordered output and native frame leases. Route supported tracks through the existing decoders and GPU imports, with software, mock and native regression coverage.
2026-10-25 10:20:02 +01:00
Davey HughesandLibretroAdmin fefcbbab58 video: keep the Android stub frame out of the shared driver vtable (#19910)
* threaded_video_test: freeze the core across the game-only lane's pause

On some runs the harness core dupes an oversize frame, which drops the
cached frame. If the game-only lane pauses right after one, the paused
redraws have no core image and gl1's game-only read fails. Where the
lane pauses depends on how many frames the lanes before it ran, so
adding a lane can move it onto one of those runs.

Freeze the core across the pause, as pixlane_once and lane_frame_mem
already do.

* video: keep the Android stub frame out of the shared driver vtable

On focus loss Android swapped video_null.frame into the current
driver's static video_driver_t, and on focus regain wrote the saved
pointer back into whichever driver was current by then. When a core
load switched drivers in between, e.g. a core restart asking for the
other HW context, video_gl2.frame became vulkan_frame: the next frame
ran vulkan_frame on a gl2_t and crashed, and video_vulkan was left
drawing nothing.

The stub is now a main_flags bit. video_driver_frame calls
video_null.frame while it is set, and GET_AUDIO_VIDEO_ENABLE still
reports video off. It holds across a driver switch or reinit until
focus returns. A focus loss before any driver exists still does
nothing.

threaded_video_test gains a lane that stubs under one driver, switches
to another and unstubs.

* video: test the stub frame on Android only

Only Android sets the stub, so other platforms take the driver's
frame without testing the flag. The flag is set whether or not a
driver is up yet, so a focus loss before drivers_init() also holds
until focus returns.

The stub-frame lane keeps its vtable and AV-enable checks everywhere
and checks for dropped frames on Android only.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-25 10:21:19 +01:00
Codex 7215b4223d hevc: retain picture parameters and slots through worker completion
Keep SPS/PPS snapshots with each decode context and retain its destination slot while the picture is in flight. Repeated parameter sets and hidden pictures can then overlap worker decoding safely. Exercise replacement, hidden output and slot reuse with delayed row publication under sanitizers.
2026-10-25 10:01:23 +01:00
LibretroAdmin f6a5bb0e95 vfs: map only descriptor-backed streams
A read-only open asking for a mapping takes the descriptor branch only
for plain files and SAF. cdrom://, smb:// and nfs:// streams stay on
their own backends and are read unmapped, so archives on a network
share open and list.

Lane: the smb_retro and nfs_retro vfs_threads_test open with
RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS, built with file mapping as
the shipping build has it.
2026-10-25 09:59:55 +01:00
libretroadmin 1de580c353 Temporal Upscaler: NGX runtime discovery and the setting
The groundwork for DLSS Super Resolution on the Vulkan and Direct3D 12
drivers, with nothing of NVIDIA's built in or shipped. libretro-common
gains two run-time loaders under gfx/nvidia, shared by frontend and
cores alike: nvapi_loader.c, the NVAPI probe the Direct3D drivers
carried themselves, now behind one nvapi_query(); and ngx_loader.c,
which finds the driver's NGX core library (beside the program, else
where the driver's registry keys point, libnvidia-ngx.so.1 on Linux),
resolves the Super Resolution entry points for Direct3D 12 and Vulkan
with the parameter accessors, and restates only the SDK interface it
takes. A sample drives both against stand-in libraries on Linux and
under Wine, and is wired into the libretro-common samples workflow.

The frontend side (HAVE_NGX, Windows and Linux builds with Vulkan or
D3D12) adds the Temporal Upscaler setting under Video > Scaling, hidden
until both the NGX core library and the user-supplied DLSS feature
library are found, and the DLSS Directory setting it is looked for in
(<system>/dlss by default), plus a System Information line that says
what was found. The setting selects nothing yet: the drivers' evaluate
path and the core-side buffer exports follow.
2026-10-25 09:53:25 +01:00
Codex f85ab4bb29 hevc: preserve visible output timing across hidden pictures
Honor prior-output discard without dropping already-final output. Tag decoded pictures with their input timestamps and use bounded header lookahead to give MP4 visible pictures their correct timestamps and durations. Add stream, backpressure, seek and partial-input regressions.
2026-10-25 09:30:20 +01:00
LibretroAdmin 56a0531bf4 slang: leave out samplers a pass never reads
rslc's tables mark each sampler active when code main reaches reads
it, as SPIRV-Cross's active interface variables do (blob v4). Both
reflection paths skip the rest, so a declared but unread history
frame, feedback buffer or texture costs nothing: the Mega Bezel
presets stop keeping three frames of history and copying into them
every frame, and an unread feedback sampler no longer gives the last
pass a render target.
2026-10-25 09:24:39 +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 77f2e5d56e menu: keep media playing while browsing its settings
Retain the current media preview across settings navigation and restore the fullscreen viewer on Back without changing playback or pause state. Cover all four menus, real browser rebuilds and visible preview pixels.
2026-10-25 08:49:06 +01:00
LibretroAdmin 3639f733c7 win32: DM_INTERLACED fallback ahead of its first use
The MSVC 2005 SDK lacks it, and the media display-rate code reads it before the modeline block that defined it.
2026-10-25 08:34:20 +01:00
LibretroAdmin 762fd1c38f slang: rslc alone in the MSVC, older Xcode and griffin builds
The MSVC projects (2012 to 2026, UWP and the legacy Android one) build
rslc through griffin_rslc.c in place of glslang and SPIRV-Cross, as do
RetroArch_OSX107 and RetroArch_iOS11_Metal through griffin.c, and the
unix griffin build in Makefile.griffin.  WANT_GLSLANG, which no
platform makefile set any more, is gone from Makefile.griffin, ngc and
wii.
2026-10-25 07:53:49 +01:00
LibretroAdmin 684394ad71 slang: rslc alone on Apple, auto rslc on Metal
rslc's MSL compiles under Apple's Metal compiler where SPIRV-Cross's
does: array, matrix and struct varyings go in one stage member per
element, column or field; length, distance, dot and normalize of
scalars, x *= M, and constructors mixing scalars and vectors or
running past a vector are spelled out; program-scope constants are
built from columns of literals, so Metal needs no global constructor;
decay and quad no longer clash with Metal's names.

Every non-bezel preset of slang-shaders gives glslang's picture on
metal, or one within the rounding where Metal's fast math orders the
two texts differently, so auto compiles with rslc on every driver.
configure builds rslc alone on macOS too, and the Xcode projects take
rslc through griffin.c in place of glslang and SPIRV-Cross.

The image gate runs metal (-d metal, pattern_core.dylib), and without
timeout(1) where macOS has none.
2026-10-25 07:43:35 +01:00
Codex 1516108f38 webm: honor BlockGroup reference flags
Use ReferenceBlock entries before or after Block to classify random-access packets, including laces. Retain the state across incremental reads and clear it on seek and rewind.
2026-10-25 06:16:13 +01:00
Codex 429f606221 media: load embedded Matroska UTF-8 subtitles
Discover S_TEXT/UTF8 tracks through the shared subtitle catalog and load
bounded cue spans asynchronously without reading unrelated media payloads.
Retain block durations and filter packets returned by cursor fallbacks.

Recognize Matroska in the internal viewer and browser. Cover sparse files,
malformed text and timing, cancellation, and all four menu adapters.
2026-10-25 03:31:30 +01:00
LibretroAdmin 08fcf5c5e0 slang: rslc alone on Linux, Windows, Android and Wii U
configure builds rslc by default, and outside Apple builds glslang and
SPIRV-Cross only when asked for (--enable-builtinglslang
--enable-builtinspirv_cross); Metal keeps them on macOS.  Android
builds rslc alone (HAVE_RSLC_ONLY=0 brings the two back), and Wii U
takes rslc in place of SPIRV-Cross.  The vendored glslang and
SPIRV-Cross stay in deps/.

rslc reads the literal strings of SPIR-V lowest byte first on every
host, as the words store them, so names and the GLSL.std.450 import
read right on big-endian Wii U.
2026-10-25 03:19:08 +01:00
LibretroAdmin 9a9b0f927f slang: auto compiles HLSL with rslc too
d3d11 and d3d12 have met the image gate through Microsoft's
d3dcompiler_47: every non-bezel preset of slang-shaders gives glslang's
picture, or one within the last bit where D3DCompile orders the float
math of the two texts differently.  Metal stays with glslang until its
xcrun run is in.

The image gate stops at once on a RetroArch that is missing or does
not start, instead of failing every preset without a log.
2026-10-25 02:49:44 +01:00
Codex 44b1f384bb webm: retain text timing and skip unrelated track groups
Expose unlaced BlockDuration without inferring duration for laces. Skip unrelated BlockGroups after their packet prefix so audio consumers avoid video payload reads. Keep refill tests meaningful by adding a large selected audio group while retaining the original video group.
2026-10-25 02:15:04 +01:00
Codex a8719b9272 media: load embedded MP4 subtitle tracks
Expose tx3g tracks through the shared player subtitle catalog and decode text incrementally. Preserve edit-adjusted cue timing, reject malformed UTF encodings and read metadata/sample ranges without ingesting movie payloads. Cover cancellation, sparse containers and all four menu adapters.
2026-10-25 02:15:04 +01:00
Codex cc7a071e6e tests: capture native views startup backtraces
Capture native initialization thread stacks when the views core never becomes ready. Keep the existing readiness deadline and screenshot checks so a stalled SDL window still fails with its fault site preserved.
2026-10-25 02:10:15 +01:00
LibretroAdmin a11f0d652a rslc: print HLSL that Microsoft's D3DCompile accepts
A ?: statement with assignments for operands is printed as if / else,
a local whose initializer reads the global of its name is renamed, a
local declared without a value starts at 0, and a function that can
run off its end returns 0 there.  d3dcompiler_47 refused all four, so
crt-royale, crt-pocket, the sharp gaussian blurs and lottesRVM fell
back to the stock chain on d3d11 and d3d12.
2026-10-25 02:41:14 +02:00
Codex ffc6a9e2b2 tests: wait for native views startup before screenshot commands
Use the existing bounded core-readiness wait for native launches as well as Wine. Slow initialization otherwise consumes the settling deadline and loses the one-shot screenshot command before its endpoint exists. A delayed native launch reproduces the failure before this change and passes afterward.
2026-10-25 02:13:31 +02:00
Codex d49b34ec38 media: skip unrelated WebM payload during audio seek
Filter SimpleBlock payload admission by audio track and bound unpublished WebM preparation to the requested span. Preserve exact PCM timing and continue across clusters with no selected packets. Cover sparse headers, metadata yields, cluster transitions and preparation footprint.
2026-10-25 00:23:23 +02:00
Codex e4cdc14970 tests: restore APNG overlay frontend fixture
Provide the configuration accessor required by the shared player. Enable LTO on Windows so unused frontend sections do not require unrelated loaders.
2026-10-25 00:23:17 +02:00
Codex f3be44b372 media: admit packed VP9 and load bounded WebM seek indexes
Windows Vulkan omitted VP9 from its packed decoder bridge and the VP9
session rejected packed GPU leases. Admit the existing GPU export path,
preserve native H.264 ownership, and retain the validated VP9 header so
reopening after a seek cannot parse released file pages.

Feed seek lookahead from its actual frontier while pinning borrowed
packets. Load optional, bounded SeekHead/Cues metadata for the video
track instead of walking a multi-gigabyte movie to locate its keyframe.
Keep sequential fallback and endpoint-clamping behavior when the index
is absent, malformed or contains no shown picture at the selected tail.
Drive menu audio in the native media fixture so its PCM clock advances.

Validation: VP9 and thumbnail-preview ASan/UBSan suites; D3D11 decoder
mocks; strict C89 threadless frontend link. The new feeder regression
fails before the correction. Exact 8K file GPU playback and repeated
30/90/180/30-second native Vulkan/WASAPI seeks reach captured frames.
The 30-second jump falls from a timeout, then 6.6 seconds, to about
0.9 seconds with matching capture hashes. Audio scanning remains a
performance limitation; this does not claim sub-half-second seeks.
2026-10-24 23:39:55 +02:00
LibretroAdmin 5affb05408 samples/gfx/rslc_image: keep the log on Windows
retroarch.exe sends --verbose to the console it attaches to rather
than to a redirected stdout, so on Windows each capture logs to a file
which the script appends to the capture's log.
2026-10-24 23:30:56 +02:00
Codex e3eead5088 menu: hide empty subtitle status during playback
Do not render the CC status label after discovery finds no subtitle tracks. The shared formatter covers Ozone, XMB, MaterialUI and RGUI while retaining loading, selected-track and disabled states.

Validated the production formatter with a strict C89 native check for absent tracks, loading, selected track and disabled subtitles.
2026-10-24 23:12:10 +02:00
Codex 08e0cec488 tests: declare decoder fixture frontend dependencies
Include the settings declaration in shared decoder stubs and supply the software-fallback stubs required by the GPU format transport fixture. Unexpected calls still fail the transport test. Run builtin-player CI when its shared decoder fixtures change.
2026-10-24 22:28:47 +02:00
Codex ddb97e55ca menu: drain previews before video-only device teardown
Video-only reinitialization destroys menu GPU resources without the preview-worker drain used by full menu teardown. A native decoder constructor can still own that device after the preview is retired. Use the existing teardown helper before freeing video.

Add an optional native held-constructor video-only reset regression. It records premature device teardown and drains safely for baseline cleanup. The baseline fails this ordering check; corrected D3D11, D3D12 and Vulkan runs complete construction before free and resume hardware playback. The native frontend build, full threadless C89 link, compile matrix and MSVC6 stand-in pass. No frame-path synchronization or new wait primitive is introduced.
2026-10-24 21:45:04 +02:00
Codex eb4264070b media: admit native decoders on the worker and recover first-frame refusals
Defer native codec construction until the decode worker owns the stream, including hardware-only AV1 metadata opens. Keep pending seeks and pause state through software recovery when the first hardware frame cannot be decoded or imported. Retain unshown inline leases on temporary drops and distinguish permanent import refusal from retryable backpressure. Cancellation retains the existing nonblocking retirement path.

Cover cold admission, queued cancellation, paused and seeked recovery, allocation and worker-creation failures, and permanent versus temporary refusal. Native D3D11, D3D12 and Vulkan tests exercise hardware playback, asynchronous first-import refusal with software recovery, and closure/reopening while construction is held on the worker. GCC/Clang sanitizers, TSan, the thumbnail-preview workflow, compile matrix, MSVC6 stand-in and a full threadless C89 frontend link pass. No new production frame-path synchronization or core HDR ABI changes.
2026-10-24 21:29:19 +02:00
Codex c9621423dc media: capture hardware decode policy before native admission
Vulkan and D3D11 native constructors read the mutable frontend hardware-decode setting. Keep that policy on the polling owner: snapshot it in the surface-player sink and gate hardware-only container admission at the menu entry point. Native backend opens now validate published capabilities without querying configuration. Existing players keep their captured policy across seeks.

Exercise the real surface-player constructor and early thumbnail admission with Off, On, explicit software-only flags, missing settings and changes after construction. The prior constructor and entry point fail these regressions. Read extracted sources as UTF-8 for the native Windows test runner.

Validation: GCC/Clang ASan+UBSan and GCC TSan handoff tests, native Windows handoff, complete thumbnail-preview workflow, compile matrix, MSVC6 stand-in and native frontend build pass. Six real D3D11/D3D12/Vulkan playback cases cover initially On and Off, change the setting during playback, then verify policy retention across captured seeks. Cold setup scheduling, first-frame fallback and intermittent AV12 latency remain separate unfinished work. No frame-path synchronization, readback or core HDR ABI change.
2026-10-24 20:41:03 +02:00
Codex f2edc19f02 tests: acquire writable slots before measuring 4K submits
The 4K surface lane writes a lent slot before asking whether the GPU has returned it. A descriptor release is not that permission. Busy retries also consume the fixed sample count, failing native D3D11 with only 23 or 24 of 60 submits accepted.

Advance the existing frame pump until the slot is writable before touching it, with a bounded failure assertion outside submit timing. Keep the copy, submit-time, release and texture-reuse checks. Add a focused selector for the same lane. Preserve failed check-driver raw logs and upload them from Windows CI so a normal failing exit retains its diagnostic context.

Validation: native D3D11/D3D12 with RGB565 and XRGB8888 accept all 60 focused submissions; full native D3D11 RGB565 and D3D12 XRGB8888 harnesses pass. The prior full D3D11 cases fail only the sample-count assertion. Actual make recipes retain failure logs and remove success logs. No production code changes.
2026-10-24 20:20:59 +02:00
Codex 36e584943d media: expose MP4 packet ranges to bounded preview feeding
Predict the next packet from sample-table cursors without reading media or advancing the demuxer. Forward that range through the video stream and shared image-transfer feeder. Admit an MP4 packet larger than lookahead over bounded reads rather than an unpaced burst.

Cover partial input, repeated and nullable queries, interleaved tracks, selection, rewind, EOF and malformed ranges. A real 1.7 MB packet beyond a16 MiB header gap fails the unpaced negative and succeeds over four512 KiB feed polls, then decodes. The CI fixture runs inline and threaded paths.

Validation: full thumbnail-preview and libretro-common workflows, GCC/Clang ASan+UBSan parser checks, native Windows parser/feeder, compile matrix and MSVC6 stand-in pass. Actual Vulkan/DXGI AV12 seeks retain matching picture hashes with active WASAPI. Phase timings separate target-ready state from test capture overhead; intermittent reported long seeks remain unresolved.
2026-10-24 19:57:59 +02:00
Codex 38781d0287 ci: classify the subtitle parser sample executables
Register the built SubRip sample targets alongside the other format tests so the sample workflow can run the parser instead of rejecting its target classification.

Validation: the complete libretro-common samples workflow builds 100 directories and runs 143 targets with zero failures, including the subtitle parser and the later sanitizer checks.
2026-10-24 19:34:02 +02:00
Codex a93dbf1d7e threads: acquire the waiter list only after claiming its lock
The retry probe tests only the lock bit. Use a relaxed pointer load there; the successful existing compare-and-swap still acquires the list. This avoids redundant ordered polling contending with the owner during a dense waiter storm. No list ownership or wakeup protocol changes.

The unchanged unrestricted 96-thread semaphore regression times out under Clang TSan and trips the GCC watchdog before this correction; both pass in about four seconds afterward. All backend checks pass Clang TSan and GCC ASan+UBSan; eight native Windows tier/timer combinations and the MSVC6/NT4 stand-in pass. The full libretro-common workflow also passes.
2026-10-24 19:34:02 +02:00
Codex dd618c0ba6 media: continue bounded seeks across contiguous input windows
Feed the next contiguous local window when a seek reaches its buffered frontier instead of yielding after one decode step. Keep the four-millisecond and 1024-step budgets, limit contiguous feeds to eight, and retain backward-boundary and sparse-input behavior.

Validation: boundary regression fails on the prior implementation and passes on native Windows, GCC/Clang ASan+UBSan and GCC TSan. Full thumbnail-preview workflow and MSVC6 stand-in pass. Active WASAPI AV12 timeline checks complete, but remain about 0.55-0.63 seconds on both versions; this does not claim to resolve the intermittent AV12 latency report.
2026-10-24 19:19:00 +02: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 bb079d496c media: match fullscreen playback to advertised display timing
Add opt-in desktop refresh matching and CRT SwitchRes source timing to the shared fullscreen viewer. Windows and X11 capture a temporary output lease and restore only when their applied mode is still current. Preserve core AV timing and reuse the owned mode across queued episode admission. Clear unstable cadence without treating an audio-only repeat tail as variable picture timing.

Validation: strict controls with GCC/Clang ASan+UBSan, GCC TSan and threadless; player loop regression and private sanitizer lanes; compile matrix, MSVC6 stand-in, complete Linux graphics and threadless menu jobs; native four-menu source tests and clean Vulkan/D3D11/D3D12 moving-pixel checks with a static negative. X11 live fractional refresh and conditional restoration pass. Windows physical refresh switching remains unverified: this session refuses CDS_TEST even for its current mode. No VRR policy or physical HDR claim.
2026-10-24 18:58:13 +02:00
Codex d48b3aacab media: publish stable movie cadence for display selection
Measure cadence from accepted movie timestamps, preserving fractional timing through inline and worker presentation. Clear evidence after seeks and reject variable or invalid intervals.

Expose cadence and source dimensions through the shared media state. Cover timestamp precision, hardware retries, threading, and real menu playback at integer and fractional rates. This prepares display integration without changing mode-switch or VRR policy.
2026-10-24 17:41:55 +02:00
Codex fcca0d5fcb tests: provide repeat control in audio mixer stand-ins
The audio driver now exposes preview repeat control. Complete the standalone audio harness mixer API so the TSan and other sample builds link without the real mixer.
2026-10-24 17:41:54 +02:00
Codex a352690514 media: add continuous folder playback and fullscreen settings
Advance naturally sorted folder movies only after clean playback completion. Retain the final frame, stop on decode errors, and expose previous/next and settings controls in all four menu drivers.

Drain terminal AV1 output and update preview audio repeat policy without reopening the player. Cover completion, queue lifetime, invalid next files, toolbar actions and native menu presentation.
2026-10-24 17:25:12 +02:00
Codex 5b757c8a63 tests: isolate the surface-pitch pattern from RGUI pixels
The pitch lane replaces the menu GPU callback, so clear the software menu texture before capturing its replacement pattern. Otherwise RGUI pixels correctly composited above the callback obscure all eight D3D12 captures after the caption-layering fix. Keep the pattern, padding, slot, filter and pixel checks intact.
2026-10-24 16:47:06 +02:00
Codex 33074397dd av1: retain decoder configuration across reader seeks
Pass the sequence OBU and resolved color to configuration-aware backends. Keep an owned bounded sequence header when initial admission or an in-band correction learns it, so reopening after a seek does not depend on the initial packet remaining resident. Reject level or tier changes during provisional admission.

Exercise configuration-only backend admission and MP4/WebM reopening with the original packet unavailable, alongside retention refusal and balanced decoder/lease cleanup.
2026-10-24 16:47:06 +02:00
LibretroAdmin dbcb4d7cf2 udev joypad: a pad's touchpad, from its own evdev node
On Linux a DualShock 4's or DualSense's touchpad is an input device of
its own beside the pad. The driver finds the one with ID_INPUT_TOUCHPAD
under the pad's HID parent, when the pad connects or when the node comes
after it, and reads its first two multitouch slots and its click into
the pad's view, which it lends. RETRO_DEVICE_TOUCHPAD and the Controller
Touchpad setting work on it; the node goes with the pad or by itself.
udev_checked_test feeds it a DualSense's events.
2026-10-24 16:28:43 +02:00
LibretroAdmin edf873fa57 net_retropad: show the controller touchpad, read as RETRO_DEVICE_TOUCHPAD
A Controller Touchpad option (off by default) has the core read port
1's touchpad itself: each finger down is a dot where it is, the screen
standing for the touchpad, green for the first and magenta for the
second. A core author has a working reader to look at, and the device
can be tried on a pad. Read by the core, the touchpad leaves the port's
Controller Touchpad mapping, which the Pointer setting shows otherwise.
It is not sent to the receiver.
2026-10-24 16:28:43 +02:00
LibretroAdmin 9e597e9d5f SDL2 joypad: controller touchpads
The sdl2 driver hands a pad's touchpad over through get_touchpad(), as
sdl3 does: up to two fingers of the first touchpad and its press, read
from SDL once a poll and only when asked. RETRO_DEVICE_TOUCHPAD and the
Controller Touchpad setting work on it with SDL 2.0.14 and later.

The SDL3 bridge test gives SDL a virtual pad with a touchpad and holds
get_touchpad() to where SDL has the fingers and the press.
2026-10-24 16:28:43 +02:00
Codex c85512ed22 tests: resolve the MSYS2 build shell before launching it
Windows CreateProcess searches System32 before PATH for a bare bash name, selecting the WSL launcher instead of MSYS2. Resolve the requested shell through PATH before compiling the menu media harness.
2026-10-24 16:27:50 +02:00
Codex ae6b6ee2e5 d3d: composite software menus after GPU media
Draw the software menu texture after the menu GPU callback so RGUI captions remain visible over its movie. Restore alpha blending and the sprite pipeline around that pass. Exercise real GPU subtitle output in the existing Windows D3D harness.
2026-10-24 15:47:42 +02:00
Codex c16cc31216 media: select subtitle sidecars through fullscreen controls
Discover a bounded catalog asynchronously, cycle tracks and Off through the shared controls, and retain the selected CPU subtitle owner across video context resets. Apply the same behavior to Ozone, XMB, MaterialUI and RGUI, with cancellation and native rendering coverage.
2026-10-24 15:47:42 +02:00
LibretroAdmin 1d4655730c CI: one Wine server for each Wine step that needs no display
Each program run under Wine started a server that left after it, and a
program started as the last server was leaving was cut off before
main() ("wine client error:0: recvmsg: Connection reset by peer"),
failing the job with nothing wrong in the tree. Steps that run Wine
programs and need no display now source scripts/wine-server.sh, which
keeps one server up for the step and starts Wine's services at once
with no output, so they cannot hold a step's pipe open. Steps that
open windows under Xvfb, start the server themselves or use a prefix
of their own are left as they are: Wine's desktop process, started by
their first window, would hold their output.
2026-10-24 15:45:40 +02:00
libretroadmin 59804e37e5 video_thread: give up the one GL context for a ring drain
With one GL context handed between the core's thread and the video
thread, the video thread takes the context after claiming a frame and
only clears the ring's busy bit once it has drawn. A drain on the core's
thread that still held the context - the menu-texture drain behind a
dupe or software frame the core pushes from retro_run with the menu up,
or any other drain reached while the core runs - waited for a frame that
could not complete, and both threads waited forever.

video_thread_ring_drain() now releases a context the calling thread holds
for the length of the wait and takes it back after, as a command round
trip already does. gl_context_drain.py runs the drain against a modelled
video thread and fails on the deadlock; it runs in the samples-gfx job on
gcc, clang and mingw under Wine.
2026-10-24 15:28:53 +02:00
LibretroAdmin 7ae92576fa slang: name the stage and file rslc refuses
A stage rslc refuses is logged once, with the stage, the pass's file
and cache key, and rslc's reason - a warning where glslang compiles it
instead, an error in a build on rslc alone - so a tester's log says
which shader to look at. shader_chain_test holds the line.
2026-10-24 15:18:59 +02:00
Claude 0b78073eb1 Revert "CI: one Wine server for each step that runs programs under Wine"
A server kept for the whole step keeps the Wine services the step's
first program starts, and they hold that program's output: a step that
pipes a Wine program into tee or grep waits for an end of output that
does not come while the server lives. Linux-msvc6-standin's Win32 input
step passed its test and then hung for an hour. Back to a server for
each program until the race is fixed without that.
2026-10-24 15:15:39 +02:00
LibretroAdmin d5f7078f95 Add RETRO_DEVICE_TOUCHPAD: a controller's touchpad for cores
A core reads the touchpad of a port's controller with the pointer's
IDs: X/Y across the surface, PRESSED and COUNT, the index the touchpad
times 16 plus the finger down. It is not the screen, so a core can
read both - a handheld's front touchscreen and rear touchpad, say
(issue #19902).

The frontend answers it from the pad's state as the HID handlers lend
it, and the SDL3 driver hands its touchpads over through a new
get_touchpad op, taken once a poll. The capability bit is set where the
joypad driver can say. A core that reads the device has the touchpad
to itself: the port's Controller Touchpad mapping steps aside until the
core is unloaded.
2026-10-24 15:14:55 +02:00
Codex 1db7ee7187 test: verify media progress throughout sustained native sizing
Keep cursor movement stable during readback and require repeated live sizing commands. A uniform-colour fixture can compare central colour across different window geometries without treating letterbox changes as playback progress.
2026-10-24 15:10:27 +02:00
Codex faf2dc9ed7 win32: retain UI font scale during modal resizing
Keep resolution-dependent UI scale stable while the native move/size loop runs. Window and media geometry continue updating, and the final scale is applied after release. This avoids repeated font atlas construction during a drag.

Exercise initial and final widget scale transitions and sustained native resize playback with modal-state cleanup checks.
2026-10-24 15:09:25 +02:00
LibretroAdmin e6ad5f7f6e samples/gfx/rslc_image: the gate on Windows, and over a whole checkout
rslc_image_check.sh runs retroarch.exe directly from an MSYS2 MINGW64
shell, with Windows paths and pattern_core.dll, so the D3D captures go
through Microsoft's d3dcompiler_47. rslc_image_all.sh runs it over
every preset of a slang-shaders checkout outside bezel/, for each
driver named, keeping each verdict in out/gate_<driver>.log so a run
cut short resumes, and lists what differs or fails.
2026-10-24 15:03:24 +02:00
LibretroAdmin fcfd5bf4eb android, msvc: build rslc into the slang drivers
Android's Vulkan slang drivers and the MSVC 2015-2026 projects now
build rslc (griffin/griffin_rslc.c) beside glslang and SPIRV-Cross, so
rslc compiles their stages with those two behind it for one it
refuses. On Android HAVE_RSLC_ONLY=1 builds without them: 13.8 MB
for arm64-v8a against 18.9 MB with them.

rslc's GLSL printer indexes its swizzle strings rather than adding to
them, which clang warns about.
2026-10-24 14:56:18 +02:00
Codex 243a1e76c1 test: recover modal WASAPI setup before media startup
Request shared stereo float output explicitly. If WASAPI is inactive after frontend initialization, reinitialize it once before the preview owns an audio voice. A persistently unavailable or substituted driver still fails the fixture.

Allow the native regression to exercise cold and permanently unavailable setup without retrying the measured playback or resize operation.
2026-10-24 14:54:57 +02:00
LibretroAdmin fff2d8cfdf samples/cores/webm_player: the AV1 test links the build's objects
webm_av1_hw_test compiled all of the player's sources again in one
command, for two defines that only rwebm_video.c and rmp4_video.c read.
Those two are compiled with them; the rest are the objects the build
already made. The four sanitized builds of the WebM player job's first
step took 25 minutes and take 18 now, under load.
2026-10-24 14:54:38 +02:00
LibretroAdmin 523e9f2926 slang: a build on rslc alone, without glslang or SPIRV-Cross
./configure --enable-rslc --disable-glslang --disable-spirv_cross builds
the slang drivers with neither dependency (SLANG_RSLC_ONLY). Passes
reflect from rslc's tables - a module without them, from the tables
rslc's ingest reads back - and take rslc's text; a frame-rectangle pass
only the SPIR-V rewrite could serve copies the frame out instead.
glcore builds its own pipelines and chains from the GLSL their SPIR-V
was compiled from (gfx/drivers/vulkan_shaders/*.src.inc), printed with
rslc's flattened blocks: each block one vec4 array, members read out at
their layout offsets, as glcore uploads them.

samples/gfx/rslc_image takes -b to hold such a build to the glslang
one; rslc_test covers the flattened blocks.
2026-10-24 14:41:05 +02:00
LibretroAdmin a7f7adc5c9 CI: one Wine server for each step that runs programs under Wine
Each program run under Wine started a server that left after it, and a
program started as the last server was leaving was cut off before
main() ("wine client error:0: recvmsg: Connection reset by peer"),
failing the job with nothing wrong in the tree. Steps that run Wine
programs now source scripts/wine-server.sh, which keeps one server up
for the step and ends it with the step. Steps that already manage the
server, or set their own trap, are left as they are.
2026-10-24 14:07:04 +02:00
Codex 9d79f9871d test: allow project booleans in Clang C89 modal checks
Match the existing Clang C89 harness flags so boolean.h does not fail on its _Bool expansion. Keep the strict declaration, warning and stack checks, and leave GCC flags unchanged.
2026-10-24 14:00:35 +02:00
Codex a175135057 test: count direct presentations without frontend threads 2026-10-24 13:28:15 +02:00
Codex f135fa0c74 test: retain static images through native window resizing
Check image visibility, presentations at the resized output dimensions and restored pixels through the real frontend loop. Ignore physical menu input while the fixture owns the window; native sizing commands still pass through Windows. Reset movie playback before measuring live A/V progress.
2026-10-24 13:28:15 +02:00
Codex 8189dee003 win32: borrow an existing fiber without taking ownership
Adopt an already-converted caller only after the floating-point isolation probe succeeds. Pump cleanup leaves that caller owned by its original user. Exercise protected and unprotected foreign callers through modal resize, cancellation and destruction.
2026-10-24 13:28:15 +02:00
Codex 5cb1efe7a8 test: read modal audio liveness through atomic flags 2026-10-24 13:28:15 +02:00
Codex df4254ca18 win32: keep fallback modal state on its owning thread 2026-10-24 13:28:15 +02:00
Codex 1e0d39dbf8 test: compare modal movie pixels after geometry settles 2026-10-24 13:28:15 +02:00
Codex c50be5f12a test: align companion stubs with the modal pump API 2026-10-24 13:28:15 +02:00
Codex 5bb9263ba1 test: cover modal pump lifecycle on native Windows and Wine
Run strict C89 native lifecycle and floating-point-state checks in MSYS2. Wine checks the supported path or explicitly reports unavailable continuity and validates fallback. Keep integrated harness window restoration asynchronous across video-owner threads.
2026-10-24 13:28:15 +02:00
Codex 92c5f83a09 test: exercise native modal media playback with live audio
Use the normal frontend message pump and external media launch, observe the actual sizing interval, move the window geometry, and require PCM and movie pixels to advance before releasing the loop.
2026-10-24 13:28:14 +02:00
Codex e164a7ed2c win32: yield modal window message loops to normal frame work
Keep message dispatch on an owner-thread fiber so sizing and moving can return to normal frontend work without recursive runloop entry or redraw timers. Preserve floating-point state, defer menu commands until normal dispatch returns, and retire message fibers on their owning threads.
2026-10-24 13:28:14 +02:00
Codex c69ded8c51 win32: keep audio builds working without frontend threads
Guard WASAPI pump-only state and make the ring-room helper available to its single-threaded writer. Link the eventcount helpers used by native Windows audio workers even when frontend threading is disabled, including the griffin build. Compile the real WASAPI driver without HAVE_THREADS alongside its existing pacing tests.
2026-10-24 13:15:43 +02:00
libretroadmin 55c2026efd samples/gfx/record_gpu_queue: fail on a stall, not on a slow machine
The threaded stress gave each side 30 seconds in all for 200000
frames, which a loaded runner under ASan or TSan does not always
manage with one slot: the handoff costs a context switch a frame. The
30 seconds now run from the last frame either side moved, so a queue
that stops still fails and one that is only slow finishes.
2026-10-24 13:03:22 +02:00
LibretroAdmin 4a979e8781 samples/audio/stretch: watch a pair's overrun, not its whole buffer
The reference chain filled a pair's output buffer, 180 KB, before each
resample and read it back a byte at a time after. With one frame a call
that was most of the run: stretch_pipeline_test took 14 minutes under
ASan and ran past its 10-minute timeout. A pair writes at most 128
frames; the 512 after its output are watched, with one memcmp. The
stretch suite with its build now takes 2 minutes.
2026-10-24 12:56:59 +02:00
LibretroAdmin 6771b8868a rcheevos: hash the first disc of an m3u with labels, BOM or quotes
The playlist reader took the whole first line as the disc's path, so an
m3u starting with a UTF-8 byte order mark, a quoted path, or a
RetroArch "path|label" entry (which RetroArch's own m3u parser and the
disk control interface accept) gave no hash, and achievements did not
load for it. The mark, the label and the quotes are now left out.

The "potential consoles" verbose message printed the extension from the
m3u path after the m3u handler had freed it; it prints the copy taken
for the handler search instead.
2026-10-24 12:56:41 +02:00
Rob Loach 22ee469999 MCP: schema check, readable MENU, smaller tool list (#19878)
* MCP: schema check on all tools, readable MENU

* MCP: MENU marks choices, answers when closed

* MCP: MENU types, shows dialogs; drop menu option

* MCP: fold INPUT, OPTION, CORE_OPTION; trim list

* MCP: MENU path, more, tab names; clearer errors

* MCP: menu title as a text line; trim the list

* MCP: trim STEPS' descriptions

* MCP: INPUT says the core gets what is held

* MCP: MENU review fixes
2026-10-24 12:59:56 +02:00
LibretroAdmin 96ea24d7d5 configuration: float rows in builds without the menu
The float rows are added whatever the build, as the bool, int and uint
rows are, so a build without the menu loads and saves the aspect ratio,
refresh rate, fast-forward and slow-motion ratios and the other float
settings too. samples/settings/float_rows checks those are listed and
that no setting of any kind is listed twice.
2026-10-24 12:52:08 +02:00
LibretroAdmin 318e8b9caa CI/samples-record: one Wine server for the GPU dispatcher's tests
Each of the three tests started and ended a Wine server of its own. One
that started as the last server was leaving was cut off before main()
("wine client error:0: recvmsg: Connection reset by peer") and failed
the step with nothing wrong in the tree. Keep one server up for the
step, as the network-clients job does.
2026-10-24 12:41:09 +02:00
Codex 348d98bf64 config: regenerate subtitle keys for headless builds
Keep generated label macros in sync with the subtitle settings. Menu-less configuration tables require the literal fallback keys.
2026-10-24 12:19:33 +02:00
LibretroAdmin 3727cef753 dbus: the screensaver worker leaves logging to its callers
The worker lives as long as the process, so a line it logged could
meet the log being torn down at exit. It now notes what it found in an
atomic word, and the callers - dbus_ensure_connection, _close_connection,
_suspend_screensaver, _screensaver_state - log those notes on their own
thread. The worker is built only where retro_atomic has pointer ops, as
it is published through one. dbus_screensaver_test checks the worker
logs nothing itself and that its findings are logged once.
2026-10-24 12:16:02 +02:00
LibretroAdmin 6626a29418 samples/gfx/wayland_present: a refresh the roundtrip cannot outrun
The pacing-wait check gave the fake compositor a 2 ms refresh. Under
TSan on a busy machine the roundtrip took longer, the vblank was already
gone, the wait rightly did not sleep, and the check and the discard
check after it failed. A 250 ms refresh is still ahead after the
roundtrip; the wait sleeps out the rest.
2026-10-24 12:15:48 +02:00
Codex facb595b63 media: enable internal movie playback without legacy cores
Default the builtin player on when a supported menu and internal MP4 or WebM decoding are available. Exercise the shipping menu workflows with the optional WebM core disabled, so external-file launch cannot depend on that core enabling the setting.
2026-10-24 11:58:58 +02:00
LibretroAdmin 1a7f04ec21 slang: rslc remaps the frame rectangle of the passes it compiles
A pass whose stages carry rslc's tables (vulkan, glcore) or its printed
text (slang_process, for d3d and metal) is remapped by rslc from the
pass's own source: slang_compile_remapped() places the members where
the SPIR-V rewrite would (slang_rect_place_pass, now exported), and the
rewritten stages keep their tables and the compiler's own GLSL, HLSL or
MSL, so no SPIRV-Cross crossing is needed for them. Stages from
glslang, or ones rslc will not take, are rewritten on the SPIR-V as
before. The slang_rect and rslc_remap harnesses take their placement
from slang_rect_place_pass.
2026-10-24 11:56:55 +02:00
Codex 8e15897704 media: continue seeks with pending local input
A bounded seek can finish its slice while waiting for a local metadata span. Requeue that work directly instead of waiting for the next UI poll. Keep external input waits yielding to their producer.
2026-10-24 11:26:24 +02:00
LibretroAdmin 2aebacc27d samples/network/nfs_retro: READ_PLUS waits for tshark and the server
The test gave tshark and the fake server two seconds to start. On a
busy machine tshark was not capturing yet, saw no replies, and the
decode check failed. Wait until tshark says it is capturing and the
server accepts a connection.
2026-10-24 11:08:26 +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
Rob Loachandlibretroadmin 265366740b sdl3: Various fixes to the SDL3 Drivers (#19879)
* sdl3: full screen at the configured monitor's size

* sdl3: drop the display server's unreachable idle_wait

* sdl3: views fixes and the swap interval cap

* sdl3: one display helper, one readback path

* sdl3: sticky views fallback, cap through wrapper

* sdl3: clip turned views, vsync falls back to cap

* sdl3: hotplug, text input, event queue, fixes

* sdl3: keep audio stream across device unplug

* sdl3: release view textures, keep a row fallback, run the SDL3 samples

The per-view textures are released once the core stops sending views,
and kept through a fallback to drawing the frame whole, whose next
frame may be a dupe that draws them again. When the interlaced
placement's batch can't be allocated, its odd rows are drawn one at a
time as before rather than not at all.

sdl3_counters now checks that a reopen keeps the stream other threads
read, and it and sdl3_keyboard run in Linux-samples-gfx against the
SDL3 the job builds, now with audio, since no workflow ran either.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-10-24 10:58:37 +02:00
LibretroAdmin 3db1d7d6a7 archive: map archives of any size on 64-bit hosts
A 64-bit host asks the VFS to map an archive whatever its size, since
pages are only faulted in as they are touched; a 32-bit host keeps the
256 MiB ceiling so a huge archive cannot take the address space the
core needs, and reads it through the stream.

Lane: archive_stream_test checks the mapping hint for an archive past
the ceiling on both word sizes.
2026-10-24 10:41:58 +02:00
Codex 703f444a02 media: scope seek continuation to seek jobs
Keep decoder completion results separate from seek progress when integrating player extensions.
2026-10-24 10:41:44 +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
Codex ea35b834c6 media: continue bounded seeks without waiting on UI polls
Distinguish useful seek progress from an input wait and requeue bounded worker steps until positioning completes or a newer request interrupts them. Coalesce nearby metadata reads into the existing 32 KiB seek window while avoiding payload read-ahead across distant headers.

Add coverage for worker continuation without UI polls, replacement targets, zero-read warm windows and nearby versus distant header I/O. On AV12.mp4 a fresh 40-minute jump measured 268 ms with preview audio enabled and a 60 Hz poll cadence; rapid seek requests still resume playback.
2026-10-24 10:30:40 +02:00
Codex 355674fcd5 rslc: bound stack use for long runtime arithmetic chains
Record proven runtime expressions so constant evaluation does not repeatedly recurse through their left spine. Emit arithmetic chains iteratively using compile-arena storage, preserving operand order and short-circuit handling. This keeps the existing 2600-term allocation-failure stress test within the default Windows stack.
2026-10-24 10:30:39 +02:00
LibretroAdmin 67674fb078 rslc: the frame-rectangle remap, in the compiler
opts.remap rewrites a stage ahead of every destination, so the GLSL,
HLSL and MSL a driver prints carry the frame rectangle as the SPIR-V
does.  Each read of a named sampler becomes a call of a helper whose
GLSL the remap writes for the pass's sampling and the front end
parses after the stage: slang_rect.c's arithmetic, operation for
operation.  A function the samplers are passed to is copied for each
way they are.  The rectangle's members go after the block's own at
the caller's offset, or into a block of their own.  The remap lives
with the printers; a build without them refuses every stage.

samples/gfx/rslc_remap holds it to slang_rect.c: the verdicts on
rslc's own module of every test shader, sampled each way, the
members' offsets, the printed destinations, clean failure out of
memory, and Mesa's draws of rslc's GLSL bit for bit against the
stage reading a texture of its own.  With --corpus it checks a whole
corpus the same way.
2026-10-24 10:24:55 +02:00
LibretroAdmin 7440364c7d archive: stream 7z and RAR archives that are not mapped
Without a mapping (smb:// and nfs:// streams, archives over the
mapping cap) the 7z and RAR backends parse through the stream the
archive layer already opened, via the parsers' read-callback APIs and
file_archive_read_at(): listing reads only the headers, and a member's
packed bytes are read when it is decoded. SMB and NFS reads continue
from the network past the read-ahead windows rather than returning
short, and a prefetch seek issued while an earlier retarget is still
pending is judged against that target.

Lanes: libretro-common/samples/file/archive_stream (unmapped VFS
answering in pieces) and samples/file/vfs_prefetch (seek behind a
pending retarget); the smb_retro and nfs_retro vfs_threads_test
read past the windows.
2026-10-24 10:23:24 +02:00
71f521e726 feat(ps3): store personal data in per-user directories (#19895)
* PS3: Store personal data in per-user directories

Resolve the active console user before loading configuration and store
personal data under /dev_hdd0/home/<id>/retroarch. Keep installation
resources shared and stop initialization if the user directory
is inaccessible.

Import external configurations into the user's directory, rebasing
personal paths while preserving the source. Support both
Sony SDK and PSL1GHT builds and add portable regression tests.

* fix (CI): missing file

* PS3: carry shared USRDIR data over to each user

A user's first boot copies the personal data of the shared USRDIR
layout into their own directory: saves, savestates, remaps, configs,
playlists, autoconfig profiles, histories, core options and the
keychain, then writes their configuration with the paths that pointed
into USRDIR moved to their directories. USRDIR itself is left as it
was, files the user already has are kept, and each file is copied
whole or not at all; the configuration is written last, so a boot
that fails part way copies again on the next one.

When the user's directory cannot be used, RetroArch runs on the
shared layout as before and logs why, instead of exiting. An imported
configuration has one copy per source, which later imports reuse as
the user left it.

---------

Co-authored-by: Jhonathan Corrêa <[email protected]>
Co-authored-by: libretroadmin <[email protected]>
2026-10-24 09:48:40 +02:00
Codex 3fe5eb89a0 menu: discard releases from presses outside the client area
Remember when a pointer press starts outside the menu bounds and discard its release even if it lands inside. Use framebuffer coordinates for RGUI and output coordinates for texture menus. Cover an outside press released over the image viewer in all four menus.
2026-10-24 09:33:57 +02:00
LibretroAdmin 03d718d688 menu: Input Information's Bluetooth line under a controller
The line saying which Bluetooth address a controller connects to was
added as one of the port's controller entries, so the controller's
display name, profile and VID/PID were shown under it a second time.
It is an entry of its own now, with nothing under it.

A controller paired with no computer holds an address of zeros, shown
as 00:00:00:00:00:00; it now says it connects to nothing.
2026-10-24 09:21:30 +02:00
LibretroAdmin b6bb208f01 samples/gfx/d3d11_video_decode: build rmp4 and rh264 once a build
Most of the tests here link the MP4 demuxer and the H.264 decoder, and
each compiled both again in its own one-command build, which ccache
cannot keep: the slowest step of samples-gfx. They are compiled once a
build now, into a directory of the compiler's and the sanitizer's own,
and linked by every test that needs them. Neither depends on a flag
only some of the tests pass. clean also removes the three tests it
used to leave behind.

Measured on two CPUs, with the same results: the Linux ASan + UBSan
check 232 s before, 157 s after; mingw-w64 under Wine 185 s, 147 s.
2026-10-24 09:21:28 +02:00
LibretroAdmin a6f0bd5248 ci_local: one ccache for every job, matched across clean copies
On GitHub each job restores a ccache of its own; here they share one.
Each job's "ccache -M 250M" therefore capped the one they all share,
and the jobs evicted each other's objects; and a clean copy is a new
directory each run, which ccache puts in a debug build's hash, so
nothing a run before had compiled matched.

ci_local now gives each step CCACHE_MAXSIZE=1G, CCACHE_BASEDIR set to
the tree, and CCACHE_NOHASHDIR, where they are not set already.

samples-tasks run twice on two CPUs: 756 s, then 419 s, every compile
of the second a hit (2030 of 2030, against 1201 before).
2026-10-24 09:21:25 +02:00
LibretroAdmin bd7f70065e samples/tasks/patch_stream: tiny feeds for small sources only
Every trial fed its source a byte, two and three at a time, sources of
up to 8 KiB among them: some two hundred million feed calls under ASan,
most of the test's time, reaching no record boundary the seven-byte
feed does not. Those three feed sizes now go over sources of up to
1025 bytes, past the 1024-byte edges; larger ones keep the 7, 64, 1024
and whole-buffer feeds.

Measured on two CPUs: 103 s before, 54 s after; 238510 comparisons of
the 280000, all matching.
2026-10-24 09:21:21 +02:00
LibretroAdmin 0466e44485 samples/tasks/patch_stream: build at -O1, and one scratch buffer
The test ran under ASan + UBSan at -O0, the slowest step of
samples-tasks. -O1, as the other samples build, keeps every check and
runs faster; and the 2 MiB buffer each of the 20000 trials built its
patches in is allocated once, as each comparison reads its own
exact-size copy of the patch, where an overread is caught.

Measured on two CPUs: 138 s before, 103 s after, the same report.
2026-10-24 09:21:09 +02:00
LibretroAdmin 6d57050cc2 libretro-common tests: CI runs the suites without coverage
Every suite was built with gcov's instrumentation and followed by an
lcov pass over the whole tree, which reads more each time as suites
add their coverage files. Nothing in CI reads the result: no report
is made or uploaded.

Makefile.test takes COVERAGE=0 to build and run the suites without
it; the default is unchanged. CI passes it, and no longer installs
lcov.

Measured on two CPUs, the unit-tests target: 201 s with coverage,
54 s without, the same fourteen suites passing the same checks.
2026-10-24 09:21:06 +02:00
LibretroAdmin c81f49e240 stack_budget: compile through ccache where it is installed
The check compiles libretro-common three times a run - the host's
compiler and two mingw-w64 ones - and every run compiled all of it,
unchanged or not. Through ccache, which keeps each .su with its object,
a file unchanged since the last run is not compiled again.

Measured on two CPUs: 397 s without ccache, 469 s with an empty cache,
116 s with a warm one, with the same report each time. CI restores the
job's cache from master's, so a run is usually warm. The 19 MB it adds
fits the job's 250 MB cache.
2026-10-24 09:21:02 +02:00
LibretroAdmin a6faac1194 ci_local: --times prints every step's time
The three slowest steps of a job say where most of its time goes, not
where all of it does: a job of fifty short steps hides its cost in
them. --times lists every step, slowest first.
2026-10-24 09:20:55 +02:00
Codex 0e07e3fc6e mp4: retain complete sample indexes for long movies
Size the sample limit from a 64 MiB per-track index budget instead of silently cutting every track at 262144 samples. Reject oversized tables and failed fragment growth instead of publishing a partial movie. Cover tail seeks across the old limit, fragmented tracks and allocation failure.
2026-10-24 09:09:27 +02:00
Codex 53944c0017 menu: check framebuffer pointer bounds in framebuffer coordinates
RGUI reports pointer positions in its menu framebuffer. Compare those positions against the framebuffer dimensions so valid clicks are not rejected by the output viewport bounds.
2026-10-24 09:08:18 +02:00
JCorrêaandlibretroadmin dd2299c786 fix(ps3): PSL1GHT menu flickering and RSX vertex addressing (#19897)
* fix(ps3): PSL1GHT menu flickering and RSX vertex addressing

Preserve the menu vertex cursor across frames to avoid overwriting
vertices that the RSX may still be reading.

Fix duplicate offsets in menu and font vertex buffers, and present
native menu frames when no core frame or software menu texture exists.

Add regression tests for vertex addressing, buffer wrapping, queued
frame preservation, and menu presentation.

* rsx: keep queued text vertices and duped frames behind the menu

The font vertex cursor now runs on across messages, raster blocks and
frames like the menu's, instead of rewinding to the start of the buffer
after each one and overwriting text the RSX has not drawn yet.

When the menu presents a frame with no new core frame, the last core
frame is drawn behind it again, since the surface it was drawn to was
cleared at the previous flip.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-10-24 09:01:52 +02:00
LibretroAdmin 4cdc15710c slang: a deferred load's compiles reuse rslc arenas
While a deferred load holds it, the slang compiles take their rslc
arena from a pool of blocks reused stage after stage - one per compile
in flight, taken with a CAS from whichever thread compiles - so a
stage costs the compiler one allocation, its blob.  A stage a block
cannot hold is compiled again on the allocator and the block grows for
the stages to come; the last holder frees the blocks.

rslc: a caller's arena survives the scratch arena's releases, and a
compile that runs out of memory at any point, in a caller's arena or
on the allocator, fails with its message instead of reading a node or
list it could not make.  rslc_test drives a long stage through every
exhaustion point of a caller's block and every refusal of an
allocator.
2026-10-24 08:38:36 +02:00
JCorrêaandlibretroadmin 6a2a9a9009 fix(ps3): PSL1GHT retroAchievements build (#19896)
* fix(psl1ght,retroachievements): RetroAchievements build

Use native recursive mutexes for rcheevos on PSL1GHT.

* PS3: build RetroAchievements and drop the mock mutex test

PSL1GHT builds now define HAVE_CHEEVOS, so the PS3 CI lane compiles
the lv2 lightweight-mutex rc_mutex backend against the real SDK and
fails if rc_compat ever falls back to pthreads there again. That
replaces the host-side mock sample, which could only check the
backend against its own copy of the SDK constants.

rc_compat.c no longer forces _XOPEN_SOURCE on PSL1GHT, matching the
other native mutex backends.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-10-24 08:37:45 +02:00
Codex 9772340bac tests: stub the media startup token in thumbnail previews
The standalone thumbnail harness links the real fade path, which now queries the frontend startup token. Supply the inactive token at that frontend boundary so the regular and threaded sanitizer targets link again.
2026-10-24 08:16:21 +02:00
libretroadmin 337c3fdfd8 CI/webOS: retry and cache the release downloads
The webOS tools and NDKs came from four release-downloader calls to the
GitHub releases API, and any one HTTP 500 failed the job. They are now
fetched by webos-deps.sh, which retries each call and keeps the assets
in actions/cache keyed by the latest release tags: a warm cache needs no
download, and an outage that outlasts the retries builds with the cached
releases. webos-deps-test.sh checks both paths against a stub gh.
2026-10-24 08:00:28 +02:00
Codex a4b8dd39e9 menu: initialize browser state before external media activation
A deferred directory refresh can retain the previous page's driver metadata. Populate the completed browser page before activating its selected media, so the action opens the internal viewer instead of reloading a content core and rebuilding the video drivers. Assert that startup never queues a content-core load.
2026-10-24 07:31:33 +02:00
Codex 44ee07043e menu: ignore pointer releases outside the client area
Title-bar mouse releases must not dismiss a fullscreen image preview. Reject out-of-window releases before dispatching them to menu drivers. Cover still-image retention in all four menus, including native Windows maximize and restore.
2026-10-24 07:19:15 +02:00
Codex 04a2a605a6 tests: check finite loops after delayed presentation releases
Require both slots to be released repeatedly, preserve frame order through two complete loops, and reach the ended state at exactly sixteen pictures.
2026-10-24 07:02:30 +02:00
Codex ee53dc8a5b menu: present external media across the full window without fades
Cover the full output viewport until the viewer is ready and suppress startup widgets. Skip hidden thumbnail fades on external launches and keep the active filename in the viewer header. Exercise widescreen startup, widget suppression and filenames across all menu drivers, with standalone fixture stubs for the new query.
2026-10-24 06:52:27 +02:00
Codex f28e370c2c tests: stress fullscreen transitions before recovery seeks
Burst seven fullscreen changes per cycle and require advancing movie pixels before seeking. A seek must not hide a stalled presentation handoff.
2026-10-24 06:52:27 +02:00
Anthony Schemel 83776d8403 ozone: Scan in the sidebar no longer freezes or jumps past the end
In the sidebar, Scan picks one of the other playlist tabs with equal
chance and does nothing when there is none. The scroll sound plays
only when the tab changed.

Test: samples/menu/playlists_scan's `ozone sidebar<N>` runs cover zero,
one and two playlist tabs.

Closes #19904
2026-10-24 06:49:20 +02:00
Anthony Schemel bfb1a09ac4 menu: Scan on the Playlists tab no longer freezes the menu
Scan on the Playlists tab picks one of the other playlists through
menu_playlists_tab_random_selection(), shared by all four menu
drivers, and does nothing when there is no other playlist.
random_range() now stays inside its inclusive range for every rand()
value.

Tests: samples/menu/playlists_scan presses Scan with zero, one and two
playlists on each driver; samples/menu/random_range holds
random_range() to its range and an even spread across it.
2026-10-24 06:49:20 +02:00
Codex 79f16fbce2 video: resume decoding after both presentation slots are released
Restart the next upload job when both jobs become idle after delayed sink releases. Cover the retained two-slot handoff without a recovery seek.
2026-10-24 06:43:26 +02:00
Codex 15df4e4afc media: batch MP4 audio seek metadata within a time budget
Keep resident timing walks within four milliseconds per preparation step instead of yielding after 256 packets. This avoids hundreds of worker wakeups on long seeks while preserving timeline mapping and decoder preroll. Add a generated two-hour AAC seek regression and PCM comparison.
2026-10-24 06:43:26 +02:00
LibretroAdmin a5fc6139fa vulkan: a deferred load builds its pipelines in parallel
The step prepares a pass in pass order - its stages, reflection,
framebuffer, pipeline layout and descriptor sets - and hands the
pass's shader modules and pipeline to a RETRO_TASK_FLG_PARALLEL task,
so the pipeline compiles run several at once beside the frame; with
an unthreaded task queue the step builds the pipeline in place.

shader_load_deferred_t.builds counts a driver's build tasks.  A
cancel waits for none to be left before the driver's cancel step
frees the chain, and the finalizing step installs the chain, or frees
it after a pass failed, once none are.
2026-10-24 06:38:43 +02:00
davey 5b4cd3d9bb samples/openxr: step Screen Distance and Width with the menu open
menu-resize and menu-resize-running walk RGUI to Screen Distance and
Width, step both with the menu open, and step Width once more just
after the menu reopens. Each step makes every screen's swapchains and
the menu's again at another size. No frame may leave out a screen or
the open menu, every quad shows all of its swapchain, every screen
image is the core's, and nothing logs an error. The walk counts rows,
so the check first confirms that the first press moved the screens.

The test layer's busy new keeps a new swapchain's images busy until
the next xrEndFrame, so every step has a frame with only the old
swapchains to show, whatever the timing. Before the previous commit
both cases fail at every step. The glcore lanes run them too; gl's
screens are cut from its window and do not resize.

Closes #19899
2026-10-24 06:25:36 +02:00
davey acea82508d OpenXR: keep a resized slot's old swapchains until the new ones show
A slot made again at another size destroyed its swapchains before the
new ones had an image, so every xrEndFrame until the release left the
slot out. Each step of Screen Distance or Width resizes every screen
and the menu, so the whole view blinked.

The old swapchains now stay on screen until the new ones are released
and are destroyed after the next xrEndFrame, under the graphics lock.
A forgotten slot drops them too, so a menu that comes back still never
shows its last image. Vulkan no longer destroys the swapchain itself
on a resize.
2026-10-24 06:25:36 +02:00
LibretroAdmin d6ae0fb226 winusb: offer to move a Wii U GameCube adapter to WinUSB
Windows' own driver cannot run the Wii U GameCube adapter: it fails to
start, so no driver RetroArch has can read it, and it reaches no port.

When the WinUSB scan finds an adapter that nothing could open, it says
so on screen once, and Information > Input Information has a row to
enable it. That moves it to Windows' own WinUSB driver, as the
DualShock 3's row does: one administrator prompt, for a copy of
RetroArch that makes the change and leaves, nothing downloaded and no
driver of RetroArch's own. The adapter then comes back on WinUSB, where
it is read. A DualShock 3 plugged in at the same time is left alone.
2026-10-24 06:09:56 +02:00
LibretroAdmin c3ba07ac8c winusb: read the Wii U GameCube adapter on WinUSB
Only the DualShock 3 was taken through WinUSB: the scan skipped every
other pad on it, the Wii U GameCube adapter among them, without a word.
The adapter is taken now, and a pad on WinUSB has its interrupt pipes
looked up rather than assumed.

The adapter takes its start (0x13) and rumble (0x11) on its interrupt
OUT pipe, as libusb sends them, not as SET_REPORT on the control pipe,
which the DualShock 3 keeps. They go the way a pad on Windows' HID
driver is written to: one write in flight, the newest waiting behind it
for a later poll, so the frontend's thread never waits on one.
2026-10-24 06:09:52 +02:00
Codex 7d60e2432d menu: hide external media startup until the viewer is ready
Keep thumbnail uploads running while suppressing the parent browser during external image and movie launches. Release presentation when the activated fullscreen view is ready, or recover on cancellation and decoding failure. Cover all four menus with captured startup frames and lifecycle checks.
2026-10-24 06:00:52 +02:00
Davey HughesandLibretroAdmin f1c6acb963 glslang: stop exporting the builtin copy to cores (#19891)
* glslang: stop exporting the builtin copy to cores

A Unix build links the builtin glslang into the executable with default
visibility. When a library in the link also defines or references
glslang (on Arch, libavdevice pulls in libavfilter -> libplacebo ->
shaderc), ld exports those definitions, and a core that bundles and
exports its own glslang binds its calls to RetroArch's older copy
instead. flycast's Vulkan renderer then crashes in GlslangToSpv on its
first shader compile.

Compile the builtin glslang with -fvisibility=hidden so it never leaves
the executable.

* spirv-cross: keep the builtin copy out of cores' reach

The builtin SPIRV-Cross objects are compiled with -fvisibility=hidden,
like the builtin glslang, so a core bundling its own SPIRV-Cross always
binds to that copy.

tools/builtin_deps_export_check.sh reads the built glslang and
SPIRV-Cross objects and fails if any of them defines a symbol ld could
export; only libstdc++ header instantiations are allowed.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-24 05:39:39 +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 4fe09508d4 av1: retain raw hardware decoders across MP4 seeks
Cancel unaccepted input and reset the explicit reference chain without reopening a healthy raw backend. Keep exported GPU leases independent. Opaque packet decoders and failed sessions retain the close/reopen fallback. Restore the cached initial HDR state before replay and cover busy restart, retained output and fallback admission.
2026-10-24 04:53:14 +02:00
Codex a0e1b02c0a media: skip AV1 picture payloads during seek metadata scans
Read bounded OBU headers and complete metadata records without requiring whole compressed samples. Refill sparse input waits within the existing worker budget and skip file gaps without reading page prefixes. Preserve the HDR checkpoint timeline and ordinary decoder waits. Cover range bounds, skipped payloads, HDR changes and sparse worker progress on Windows and Linux.
2026-10-24 04:53:14 +02:00
LibretroAdmin 302f6a69d9 d3d11/d3d12: a deferred load builds its passes in parallel
A deferred slang load on d3d11 or d3d12 compiled one pass after
another: the step processed a pass, ran D3DCompile on both stages
and made its objects before the next pass began, so a long preset
spent its load in fxc one stage at a time.

The step now processes the pass (slang_process, which needs the
chain's semantics) and hands its HLSL to a build, which compiles the
stages and makes the shader objects or the pipeline and the constant
buffers.  The builds are RETRO_TASK_FLG_PARALLEL tasks, so with extra
workers several passes compile at once beside the frame; with an
unthreaded task queue the build runs in the step as before.

- The load state is reference counted: the load holds one, each
  build one, and the last to let go frees what the passes made.  A
  cancelled or failed load frees its LUTs and preset at once; builds
  still running finish into the state and drop it.
- Each build holds a reference to the device (and on d3d12 to the
  root signature) for its run.
- A build that fails marks the state; the next step ends the load as
  a failure in the step did.
- The finalizing step waits, one frame at a time, for the builds
  still running before it installs the chain.
- d3dcompiler_common: D3DCompile is resolved once, by
  d3d_compiler_load() at the load's begin, before any build runs;
  the builds only read it.  A load whose compiler cannot be loaded
  steps on the chain thread as before.
2026-10-24 04:49:44 +02:00
Davey Hughesandlibretroadmin deebdf6a4c libretro-common: stop exporting MD5 and krb5 symbols (#19892)
* libretro-common: stop exporting MD5 and krb5 symbols

libretro-common's MD5_Init/Update/Final and krb5_encrypt/decrypt/
string_to_key share their names with OpenSSL's libcrypto and MIT
krb5's libk5crypto. When either library is in the link, ld exports
the executable's definitions and their callers bind to them instead:
libssl's MD5_Init lands in RetroArch's copy, whose MD5_CTX is 152
bytes against OpenSSL's 92, and so do the MD5 calls of a core that
bundles LibreSSL (citra).

Compile md5.c and net_krb5.c with -fvisibility=hidden so these
definitions stay inside the executable.

* libretro-common: hide MD5 and krb5 in the sources, check it in CI

Hide md5.c and net_krb5.c's definitions with a visibility pragma in
the sources instead of a Makefile.common flag, so griffin builds and
cores carrying libretro-common get it too. net_krb5.c's includes move
above the pragma, which would otherwise hide libc and socket
declarations. The pragma is popped at the end of each file, so
nothing after them in griffin.c is affected.

The Linux CI job checks that neither object exports a function.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-10-24 04:45:49 +02:00
Nelfe80andlibretroadmin 0afe7f41d1 netplay: record confirmed multi-player input in BSV replays (#19870)
* replay: accept captured normal states for BSV checkpoints

Let netplay supply a frame's saved state after confirmation without serializing the current speculative core state. Preserve the existing on-disk format and local recording path.

* netplay: commit BSV input frames after rollback confirmation

Stage the input queries seen by the core and replace them during rollback. Commit each confirmed frame once, using portable initial states and checkpoints captured at its emulated boundary. Keep local input sampling and stalled iterations out of the movie. Reject unsynchronized starts and stop at state discontinuities.

* test: cover confirmed netplay BSV recording and offline replay

Exercise the shipping recorder and rollback loop with delayed input, ring wrap, mid-session starts, stalls, absent ports and checkpoints. Compare host, client and spectator recordings, then replay through the ordinary BSV reader. Add sanitizer and Windows CI coverage.

* netplay: keep replay state access in the input frontend

Keep recording flags, checkpoint requests and notifications in the input frontend. Exercise the same frontend calls in the netplay BSV harness.

* netplay: free BSV staging and keep netplay movies out of savestates

Free each staged frame's states once it is committed, and drop all
staging when a netplay recording stops, so a session no longer holds a
normal savestate per ring slot. Skip the speculative initial state at
record start, keep netplay movies out of savestates since they lag the
core by unconfirmed frames, and keep the recorder's state in flag words.

The netplay BSV tests check that committed and stopped recordings keep
no states, and the MinGW lane links statically like the other wine lanes.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-10-24 04:34:26 +02:00
LibretroAdmin 3a1e76bd64 input: a controller's touchpad as the port's touchscreen or mouse
A per-port "Controller Touchpad" setting says what the touchpad on the
port's pad is to its core, where the pad has one:
- Touchscreen, the default: each finger on the pad is a touch on the
  pointer device at that place on the screen, two fingers two touches;
  while no finger is on the pad, the screen's own touches are read.
- Mouse: moving a finger moves the port's mouse, added to the mouse's
  own moves, and pressing the pad in clicks - a right click with two
  fingers on it.
- Off.

The pad's state is read through the joypad driver's view, as the poll
left it, so the touch is as new as the buttons and nothing is copied.
A mouse's moves are worked out once a poll, at the first ask.
2026-10-24 04:29:37 +02:00
LibretroAdmin 707a29fb6e hid: read the DualShock 4's and DualSense's touchpad fingers
Only the DualSense's touchpad click was read, as a button. Both pads
send two fingers in every full report: each one's place, and whether it
is down. The handlers now keep them in the pad's view, where the
pointer device puts a place, with the pad's press beside them, so the
frontend reads them with the buttons at the poll, through the view the
joypad driver already lends, with nothing copied. A finger's x and y
are packed into one word.

The two pads share the layout, read by sony_pad_touch.h, with the reach
SDL gives each. Other makes' pads are not known to lay theirs out the
same and are taken to have none.
2026-10-24 04:29:34 +02:00
LibretroAdmin 8d1dd59093 task_queue: parallel tasks on extra workers; shader stages use them
RETRO_TASK_FLG_PARALLEL lets a task run beside any other on extra
workers that take only tasks marked so; every other task keeps to the
one worker, one at a time as before, and no task is in two handlers at
once. The frontend sets how many extra workers start (cores less
three, from retroarch.c); none by default. Detachable and main-thread
tasks ignore the flag, and the GCD runner, whose tasks already run on
the system's concurrent queue, is unchanged.

A deferred shader load pushes up to four parallel compile tasks, each
taking the next pass and compiling a file once however many passes
name it; the stepper lists what they finished before each step and
parks until the pass it needs lands. The blob table's claim moves with
the stepper, whose calls may each run on a different worker or GCD
thread. task_queue_parallel_test covers the runner.
2026-10-24 04:12:55 +02:00
LibretroAdmin 2ec3607c97 Bluetooth (Windows): show a pairing's progress on screen
Selecting a device showed nothing: whether it paired went only to the
log. A pairing now says on screen when it starts and whether it worked,
and a Wii controller that could not be paired gets a reminder to press
its sync button and select it again while its lights blink.
2026-10-24 04:08:45 +02:00
LibretroAdmin f448ad267d Bluetooth: a scan holds the driver, so quitting during one cannot crash
The scan task calls the driver from the task thread while the frontend
can stop the driver from its own, so quitting during a scan freed the
driver under the task, which then ended the scan on freed memory and
corrupted the heap.

The driver started is now held by a count: the frontend's hold, and one
taken for each scan, on the frontend's thread, when the scan task is
pushed. Stopping the driver lets go of the frontend's; whichever lets go
last frees it, so a scan running finishes on a live driver. No lock and
nothing waited for.
2026-10-24 04:08:42 +02:00
Codex 9e2361b8be tests: wait for running content before Wine framebuffer captures
A fixed startup sleep can capture a cold Wine prefix before the application has started its core. Require a PLAYING or PAUSED GET_STATUS response before settling and comparing frames. Keep the existing retry for a loader that exits before logging.
2026-10-24 03:59:43 +02:00
Codex 24a1d1aab0 video: initialize notification visibility without a menu 2026-10-24 03:59:43 +02:00
Codex 974160c34e imageviewer: release the file list on unload and failed load 2026-10-24 03:59:43 +02:00
Codex 2c1cb91912 runloop: bound the paused frame period for unknown display rates
Use the existing frame-time helper when a paused driver reports no refresh rate. Dividing by zero produced an invalid timer margin and a signed overflow in the imageviewer sanitizer smoke run.
2026-10-24 03:59:43 +02:00
Codex bff85f0fd9 h264: retain hardware decoder resources across MP4 seeks
Restart parser, reference and output ordering state while retaining a healthy backend session and its slot ownership. Keep the existing reopen fallback on software, broken sessions or allocation failure. Exercise repeated restarts against the checking backend.
2026-10-24 03:59:43 +02:00
Codex 3b4a142477 media: reuse software WebM decoders for nearby forward seeks
Continue from reconstructed references when the target is ahead and no later cue offers a shorter path. Preserve held packets, honor lowered residency bounds and restart for backward or unsafe replacements. Keep a warm preview file window when its new cursor is still resident. Generated pixel, cancellation, timing-prefix and counted-I/O tests guard both paths.
2026-10-24 03:59:43 +02:00
Codex 274157dca9 Fix image inflate shift and D3D9 harness context dependency
Use unsigned arithmetic when filling the inflate bit buffer. Supply a fail-fast context release stub for the D3D9 harness, whose device path does not release a GL context.
2026-10-24 03:59:43 +02:00
Codex 4b8a0ebcb9 tests: prevent session bus autolaunch in isolated views cases
An unset bus address invokes dbus-launch during SDL startup. Give each headless case an unreachable address under its private runtime directory, so startup cannot wait for an autolaunched desktop session. Four blocked-launch SDL cases fail before this change and render successfully with it.
2026-10-24 03:46:50 +02:00
LibretroAdmin 1bbd7eda12 autoconfig: the DualSense's built-in profiles open the menu with PS
The hid driver's DualSense handler gives its PS button as button 16 and
already opens the menu with it, but its built-in profiles bound nothing
there, so Menu Toggle showed None. They now carry it, so it shows as
the profile's and a saved profile keeps it.
2026-10-24 03:04:17 +02:00
Codex 1fa7a53f5b vp8: retain decoded arenas instead of copying reference planes
Bind last, golden and altref to completed frame arenas and decode into an unreferenced fourth arena. Preserve reference-copy ordering and borrowed-plane lifetime, and release retained macroblock state if an arena allocation fails. Generated independent pixel references cover hidden frames, cropped geometry and resolution changes; counted large copies and injected allocation failures guard the ownership path.
2026-10-24 02:47:53 +02:00
LibretroAdmin fc62101469 Bluetooth (Windows): answer a Wii controller's PIN, and never hang on quit
Windows asked the user for a Wii controller's PIN, which is the radio's
own address in binary and cannot be typed. The driver now registers for
the device's authentication and answers its PIN request itself, falling
back to the older call without Vista's functions. A device whose name
the search had not heard yet is looked up again before pairing, so a
Wii controller is not taken for another device.

Quitting while a pairing waited on Windows hung, as the driver joined
its thread. A thread still running is now let go, and the last of it
and the driver to finish frees the driver.
2026-10-24 02:42:28 +02:00
LibretroAdmin de7916ec3f configuration: Save Controller Profile keeps the profile's hotkeys
A profile's hotkeys live with the controller's profile, not the port's
binds, so a profile saved under the device's name - a new file when it
came from a downloaded profile or a built-in one - lost every hotkey it
had, such as a Menu Toggle on the pad. They are written to the saved
profile now, with their labels.
2026-10-24 02:42:22 +02:00
Codex c3c7db8ee7 menu: reload active file media after a window context reset
Ozone and XMB must reopen the selected fullscreen file even when ordinary thumbnails are disabled. Exercise application fullscreen transitions with real movie pixels and seeking across all four menus, with direct and threaded GL presentation. Windows tests can require the DXGI bridge.
2026-10-24 02:39:33 +02:00
Codex 6c45d46dcc vulkan: route NVIDIA 610.47 H.264 through shared D3D11 exports
The Windows driver corrupts H.264 reference pictures through Vulkan Video, including in independent FFmpeg decoding. Reuse the matched-adapter packed GPU export path without CPU readback. Keep other drivers on Vulkan Video and leave correct software fallback when GPU interop is unavailable. Check generated H.264 exports against software, including cropping.
2026-10-24 02:39:33 +02:00
Codex 9a50bafdc0 io: retain the reserved media window file cursor
Avoid restoring a cursor that no prefix fill consumes. Consecutive window reads no longer seek; metadata probes still leave the residency frontier untouched. Keep cursor restoration for prefix fills and whole-file fallback. Count reads and seeks in the generated window regression.
2026-10-24 02:39:32 +02:00
LibretroAdmin 8ae1e34cd5 d3d11/d3d12: a deferred load's passes are built on the task thread
A pass is slang_process, two D3DCompiles and the device's object
creation, all free-threaded; the context and the queue are touched by
begin and by the finalizing step only, which stay on the video thread.
The begin says so, and reads into the load's state the frame-rectangle
flags the step used to read of the driver. fxc's time on a heavy pass
then runs beside the frame instead of stopping it.
2026-10-24 02:36:55 +02:00
Codex fa9fa373dd tests: supply the GL queue harness context-release dependency
Abort if the scripted fence queue unexpectedly reaches single-context handoff. Keep the production publication path linked after it began calling the context-release helper.
2026-10-24 02:17:39 +02:00
Codex e14a6c2064 tests: complete synchronous shader harness dependencies
Fail fast if deferred shader ownership reaches the synchronous cycle harness. Normalize fixture and expected path separators for native Windows, preserving preset identity and order checks.
2026-10-24 02:17:38 +02:00
Codex a3dd4a8543 media: retain WebM sound metadata before seeking its window
Create the prepared sound while the initial header is resident, before seeking can decommit earlier cluster headers. Retain its window owner immediately so cancelled preparations release borrowed data safely. Exercise later WebM seeks and cancellation with float and s16 output against resident reference audio.
2026-10-24 02:17:38 +02:00
LibretroAdmin 11548585f4 DualSense Edge: read the back and Fn buttons
The Edge sends its Fn buttons and its two back buttons in the high
bits of the third button byte, which the handler dropped, so they could
not be bound. They are now buttons 19 and 20 (Fn) and 21 and 22 (back
buttons, left first), as SDL reads them. The Edge also gets the
DualSense's built-in autoconfig under its own name.
2026-10-24 02:06:47 +02:00
LibretroAdmin 9611cf8282 video: a refused precompile task is freed by its pusher
task_queue_push() refuses a task without running or freeing it, so
the shader precompile's task was leaked on that path, with a comment
saying the queue had freed it. The pusher frees it.
2026-10-24 01:54:05 +02:00
LibretroAdmin 896c4f4252 Shaders: vulkan builds a deferred load's passes on the task thread
A driver whose pass step may run on any thread says so from its begin
(the chain is not on screen before the finalizing step, and a pass's
objects are made at device level), and the load's task then runs the
steps themselves, one per call, marking the load busy around each so
the main thread's tick waits; the finalizing step stays on the chain's
own thread. vulkan opts in. The blob table serves one thread at a
time: the task claims it for the load, the other threads' lookups
answer nothing meanwhile, a listed blob whose last reference goes on
any thread is parked on a dead list the table drains at its next
operation, and the counts are atomic. The debug object counters in
vulkan_common are atomic for the same reason.
2026-10-24 01:39:10 +02:00
LibretroAdmin ccebe0a707 video_thread_hw: the one context is given up before the push waits
On the single-context ring the push held the context across its
waits on the next slot's fence, which the video thread signals under
the same token, and the video thread took the token before claiming
a frame, which a push waiting for ring space depends on. The push now
gives the context up as soon as the frame is captured; one context
runs its commands in order, so the cross-thread fence waits are not
needed on it and are skipped. The video thread takes the token after
its claim.

egl_hw_core's shim gains a swap slower than the push's wait for the
video thread's turn, and the lane runs the one-context case through
it.
2026-10-24 01:18:12 +02:00
Codex 7a3bcbdc28 media: route image and movie activation to the internal viewer
Open supported external media in the fullscreen menu viewer and keep image confirmation there across all four menu drivers. Disable the legacy imageviewer core in normal build configurations while preserving its sources and Griffin guard.
2026-10-24 01:00:53 +02:00
LibretroAdmin f2d338b9e7 Shaders: a deferred load compiles its passes on the task thread
One task per load compiles the preset's passes ahead, one per call,
detached from the chain's thread: it reads, compiles and writes the
cache as that thread would, but asks and tells the blob table nothing,
prints no text and falls back to no other compiler. Each pass's stages
land in a slot; the chain's thread offers the ready ones to the table
before its step, so the step's own compile takes them by reference,
and the load's tick holds a step back while its pass is still with the
worker. Only with a threaded task queue, and only where rslc is the
compiler; what a chain never took is released unlisted by the last
holder. shader_chain_test covers the detached compile and the offer.
2026-10-24 00:30:03 +02:00
libretroadmin 8b6754f929 configuration: pin command-line overrides to their own rows
log_to_file and netplay_check_frames are generated rows now, so the
SETTING_OVERRIDE that followed each by hand landed on whichever row
was added last. With --log-file or --check-frames, config_save_file()
then skipped that unrelated setting and wrote the command-line value
into the config. SETTING_OVERRIDE_FOR finds the setting's own row,
and only once its override is set, the one time it is read.

cli_override_rows_test sets every override and checks each lands on
its own key.
2026-10-24 00:29:32 +02:00
Codex af024c639f win32: restore owned display modes on their original outputs
Keep each changed output's original DEVMODE and last successfully applied mode. Teardown queries that named output and restores only while the applied mode remains current; external changes and disconnected outputs end ownership. A later successful switch after an external change saves that newer desktop state.

Allocate bookkeeping before applying a first change, preserve the first original across repeated owned changes, and free records after failure or teardown. This replaces primary-screen geometry and current-window restoration.

Legacy/current ANSI/Unicode profiles pass GCC/Clang ASan+UBSan, Wine and native Windows, covering two outputs, repeat switches, external changes, disconnect and allocation failure. An unconditional-restore negative control fails. Real Windows-header/read-only checks and actual Wine/Xorg listed-mode switching pass; physical multi-monitor acceptance remains open.
2026-10-24 00:26:04 +02:00
Codex 338d695310 win32: keep mode enumeration on the captured monitor
Capture the monitor once for a resolution change, then read current settings, enumerate candidates and apply the mode to that same device. A window move must not combine one output's mode with another output's name.

Legacy/modern ANSI/Unicode profiles pass GCC/Clang sanitizers, MinGW/Wine and native Windows. The monitor-recapture negative control fails. Real Windows-header builds and read-only display queries pass in ANSI and Unicode; no physical mode was changed.
2026-10-24 00:21:25 +02:00
Codex 164a8037e4 win32: report rejected display mode changes as failures
Stop if current mode discovery fails and only report success after Windows applies the requested mode. No matching advertised mode, failed validation, failed application and registry-not-updated results must not be accepted as a successful refresh change.

Seven outcome cases pass strict C89 in legacy/modern ANSI/Unicode profiles with GCC/Clang ASan+UBSan, MinGW/Wine and native Windows. The previous setter fails the result assertion. Tests mock the mode API and do not change a physical display.
2026-10-24 00:17:50 +02:00
Codex bb2ed09616 x11: identify the output under the current window
Return the connected output name and geometry from the same CRTC under the window center. Before window creation use the first active output; clear failed queries rather than leave stale results. Handle disappeared output records and bound copies by the RandR name length.

GCC/Clang ASan+UBSan fixtures cover two outputs, movement, query failure, off-output coordinates and short names; the old getter fails the new test. C89 compilation passes with and without RandR. A real isolated Xorg dummy server passes output identity, modeline switching and restoration; physical multi-monitor acceptance remains open.
2026-10-24 00:14:06 +02:00
Codex dc1635faa6 win32: report output identity with its captured geometry
Capture one monitor for both the output name and current display settings. Clear failed queries, handle missing monitors, and convert Unicode device names to bounded UTF-8. This supplies the output identity needed to bind future media display restoration to the correct monitor.

Exercise legacy and modern ANSI/Unicode paths, monitor movement, failures and bounded buffers with GCC/Clang sanitizers and MinGW. Native Windows ANSI/Unicode read-only queries, full frontend startup and the MSVC6 standin passed. No native physical display mode was changed.
2026-10-24 00:09:07 +02:00
Codex e1de7cd1ee tests: supply the deferred shader cancellation boundary
The directory-cycle harness links the real shader parser without the video driver. Supply its new cancellation symbol and abort if the synchronous harness unexpectedly enters that path.

Validated the five directory-cycle scenarios with GCC and Clang under AddressSanitizer and UndefinedBehaviorSanitizer.
2026-10-24 00:09:07 +02:00
Codex a04a00ca7a modeline: bound scale selection for invalid timings
A listed server mode with zero refresh makes the incrementing scale search loop forever. Compute the first candidate directly, reject non-positive and unrepresentable inputs, and guard integer and floating products.

Cover zero-rate mode rejection, integer overflow, extreme scales and fractional rounding. GCC/Clang sanitizers, MinGW64/Wine and native Windows tests pass; the complete modeline golden matrix remains unchanged.
2026-10-23 23:52:34 +02:00
Codex 366c448c17 x11: contain errors from rejected modeline requests
Do not forward a modeline connection error to Xlib after trapping it: the default handler terminates the process when a server rejects a mode. Forward only errors from other connections, preserving their handler result without contaminating the modeline failure flags.

Exercise rejected additions and unrelated connections through the real backend. Run the production handler separately on Windows without Xlib, and require the original handler to fail the regression assertions.
2026-10-23 23:52:34 +02:00
LibretroAdmin 6c2a03c18b Shaders: the deferred load runs under threaded video too
The load belongs to two threads: the main thread starts it, ticks it
and tells the menu; the thread the driver runs on builds the passes.
The wrapper forwards begin as a waiting command and each step as a
posted one that runs between frames with the load marked busy, the
clear of which publishes the step's writes; the main thread reads
nothing of the load while it is set. A cancel - another load, the
driver going away - is the one step that waits, and the wrapper makes
the writes the driver reads as a cancel on its own thread, so no field
is written from both. rslc_image_check.sh gains -D (apply the preset
after the first frames, taking the deferred path) and -t (the same
under threaded video).
2026-10-23 23:44:18 +02:00
LibretroAdmin 902783cf31 Tests: views e2e asks the window manager again when a probe is slow
A slow xprop on a loaded runner is not an answer about openbox, so
the readiness poll goes on until its deadline instead of ending the
run on the first probe that takes longer than its timeout.
2026-10-23 22:47:32 +02:00
LibretroAdmin edd32635da glcore: passes binding the same two blobs share one linked program
A linked program is a function of its two stage blobs alone, so it is
held once and referenced by every pass that binds that pair, within a
chain and across the chain being built and the one it replaces; the
last pass to let go deletes it. glcore keeps the outgoing chain until
the new one is built, as vulkan does, so a reload, an append or a
preset switch over a live chain neither compiles nor links the passes
it keeps. gl3_chain_alloc_test counts the programs.
2026-10-23 22:43:12 +02:00
LibretroAdmin 01fc649014 slang: live stages are listed in a blob table and reused
The blobs of every compiled stage are listed under the pass's cache
key while anything holds them, and a pass with that key takes the
listed blobs by reference instead of compiling: a .slang two passes
name compiles once, and a chain built while the one it replaces is
alive compiles only the passes the live chain lacks. The table holds
no reference, so a blob leaves it with its last holder; it belongs to
the thread that builds chains. vulkan keeps the outgoing chain until
the new one is built. samples/gfx/shader_chain counts it: load, append,
remove, prepend and reload through the real slang_process.c.
2026-10-23 21:47:03 +02:00
LibretroAdmin d15cddd58c slang: the compiled stage is one refcounted blob
A stage's words and the compiler's reflection tables travel as one
slang_blob from the compile or the cache hit through slang_process()
into the vulkan and glcore chains, which hold references instead of
copying words. slang_reflect_pass() builds the chain's reflection from
the tables when both stages carry them, with the checks the
SPIRV-Cross path makes, and asks Cross only for a glslang stage or one
the frame-rectangle pass rewrote; a pass whose text and tables both
came from rslc never opens a Cross context. The blob format gains the
descriptor set on blocks and samplers (version 3). slang_rect returns
rewritten stages instead of freeing its inputs. samples/gfx/
slang_blob_reflect holds the table-built reflection against the
Cross-built one over every corpus pass: 1631 passes, 0 mismatches.
2026-10-23 21:18:31 +02:00
Codex 5333a7c5a5 Include exception declarations used by SPIRV-Cross containers
Clang's updated libc++ no longer supplies std::terminate through the container header's other includes. Include exception directly so CLANG64 and CLANGARM64 MSYS2 builds can compile the bundled shader translator.
2026-10-23 15:42:27 +02:00
Codex d1a5579273 Prepare single GL context ownership in poke installation
Establish the existing handoff token on the video thread while installing its poke table. Keep the context owned through remaining work on that pass and release it at the normal end, so core startup no longer sends a second blocking release command. Preserve the token when core context creation fails so renderer commands and teardown can still acquire the context.

Validate the unchanged wait budget, strict C89 ownership fixture on Windows and Linux sanitizer/Wine toolchains, a fresh GL frontend build, real-GL threaded harness and MSVC6 stand-in. Native single-context device acceptance remains separate.
2026-10-23 15:30:12 +02:00
Codex bd5a0265f2 Let setup-msys2 manage runtime upgrades
The dependency retry's pacman -Syu upgrades msys2-runtime inside the running shell and terminates every matrix job before configure. Use the setup action's update phase for system upgrades and retain bounded dependency-only retries with mirror rotation.
2026-10-23 15:20:54 +02:00
LibretroAdmin 01f36abbd1 gl2: the ring's state on the core's thread apart from flags
Under the hardware ring the core's thread set and cleared the ring
bit in gl->flags and read the sync capability from it, while the
video thread moves other bits of the same word every frame. The ring
bit is now its own atomic word, stored on the core's thread and
loaded on the video thread, and the capture reads the sync
capability from a field written once at init.
2026-10-23 14:58:16 +02:00
Codex cd005310ec CI MSYS2: refresh package databases when retrying mirrors
Refresh databases and upgrade in the same transaction as package installation. A rotated mirror can otherwise serve packages that fail the checksums in a stale cached database on every retry.
2026-10-23 14:33:59 +02:00
Codex 0b4e74ad97 CI views: use software SDL rendering on Xvfb
Wait for the window manager readiness property before launching fullscreen SDL cases. Keep sdl12-compat on its software renderer so CPU views checks do not depend on concurrent headless GL startup.
2026-10-23 14:33:59 +02:00
Codex 01a88010f0 Defer video reinit until native recording resources retire
Finish native GPU recording before resetting its video device. Coalesce pending reinit flags and apply them once through the existing main-thread recorder poll after all native users retire. Drop superseded requests during content or quit teardown, with no waits, locks, frame allocations or new runloop call.
2026-10-23 14:20:40 +02:00
LibretroAdmin ffbe9957c9 video_thread_hw: one GL context, handed between the threads
A context that cannot give a hardware core a shared context - the
Switch, where nvservices allows a process one GL context, or any EGL
whose share context could not be made - declares it with
GFX_CTX_FLAGS_NO_SHARED_CONTEXT. The GL drivers then make none, and
the ring hands the driver's one context between the threads instead:
the core's thread holds it around the core's work and gives it up
around the present and around blocking commands to the video thread;
the video thread holds it for a pass of its loop. The owner moves by
compare-and-swap on one word and parks on an eventcount; the core's
thread takes it back after the present it released for. The core's
thread binds it off the window surface, the video thread with it.

SET_HW_SHARED_CONTEXT stays refused on the Switch; nothing changes on
a context that shares. egl_hw_core's shim gains a mode that refuses
the share context, and the lane runs the handoff end to end, with the
core that caches its framebuffer too.
2026-10-23 14:09:44 +02:00
Codex 255f4be846 Bound HEVC weighted prediction reference loops locally
Validate and snapshot each active list count before indexing its fixed flag and weight arrays. Use the bounded snapshot for all loops so optimization cannot lose the limit across parsing and writes. Exercise capacity boundaries and malformed counts at O3, with the regression wired into both HEVC sample workflows.
2026-10-23 14:03:26 +02:00
LibretroAdmin 2098ef1252 rslc: auto picks rslc on vulkan and glcore
Both drivers have met the phase-8 gate: the corpus with zero
reflection diffs, and every preset of slang-shaders outside bezel/
(1166) byte-identical to the glslang+SPIRV-Cross build on both, the
crt-yah presets aside, which read the clock.  video_shader_compiler
= 0 now resolves to rslc for a caller that wants no text (vulkan) or
GLSL text (glcore), glslang staying behind it for a stage rslc
rejects; d3d10/11/12 and metal, which ask for HLSL or MSL, stay with
glslang until the fxc and xcrun runs of the same gate are in.
2026-10-23 13:42:25 +02:00
LibretroAdmin d54d2128f6 rslc: print a constant vector component as a swizzle
v[0] and v.x are the same component, but Mesa compiles the two apart
and the rounding differs: Anime4k on glcore was one 8-bit step off in
the luma its first pass stores.  The text skins now print a constant
index into a vector as the swizzle, as SPIRV-Cross does.
2026-10-23 13:42:25 +02:00
LibretroAdmin d1a02ef398 rslc: print a matrix over a scalar as the SPIR-V computes it
The GLSL, HLSL, MSL and CafeGLSL skins print M / s as M * (1.0 / s),
the form the SPIR-V emitter and glslang lower it to, so the text a
driver compiles rounds like the module the Vulkan driver runs: the
nedi presets on glcore were one 8-bit step off.
2026-10-23 13:42:25 +02:00
LibretroAdmin b6f7e2c448 rslc: branch, select and divide matrices as glslang does
The full-corpus image gate found presets off by one 8-bit step from
the glslang build: a matrix divided by a scalar, which glslang lowers
to the matrix times the reciprocal, and ?: and && / || shapes, which
the JIT behind lavapipe rounds differently as OpSelect and as control
flow.  The emitter now mirrors glslang's rules - OpSelect only when
both sides are a variable or a constant, OpLogicalAnd / Or only over a
trivial right side, OpMatrixTimesScalar by 1 / s - so a driver's
compiler sees the same shapes from both modules.
2026-10-23 13:42:24 +02:00
LibretroAdmin ed1fef95d0 rslc: CafeGLSL flavour of the GLSL printer; wiiu/slang goes C89
RSLC_DST_GLSL_CAFE prints a stage the way the Wii U lane has always
handed it to Nintendo's compiler: #version 150, the uniform and push
blocks as the two uniform blocks the GX2 driver fills by member name,
a location on every in and out, samplers under their source names,
GL_ARB_shader_bit_encoding where 150 needs it.  wiiu/slang/slang-cafe
(C89: RetroArch's own include expansion plus that printer) replaces
slang-parse.cpp, its peglib grammar and the glslc -E step, so the
text the Wii U compiles is the IR every other driver compiles.
tools/rslc-cafe-check.py validates the print of every corpus stage
as GLSL 150 next to SPIRV-Cross at the same version.

rslc_image_check.sh captures a differing preset a second time with
glslang and reports one that varies between two glslang runs as
time-dependent rather than as a difference.
2026-10-23 13:42:24 +02:00
Codex 4b4c0a8b5c Keep Media Player sublabel lookup in enum order
The shared sublabel table uses binary search. Place Media Player before File Browser to match the generated enum values; the visible Load Content menu order stays unchanged.
2026-10-23 13:41:19 +02:00
Codex 4ac5f8954b Verify recording PCM across device stop and resume
Keep the stopped-output regression tied to recording behavior: all core callback styles must still deliver complete PCM to the recorder while device writes are disabled and after output resumes. Verify both frame counts and sample values.
2026-10-23 12:46:47 +02:00
LibretroAdmin 0052ff6c72 audio: core audio against a stopped driver is dropped, not written
A driver audio_driver_stop() has stopped - the audio toggle, a window
size/move, CoreAudio's interruption - drains nothing, so a blocking
write to it never returns; a core that emits audio from
retro_serialize() or retro_unload_game() past that point hangs the
frontend there. The threaded commit already lets go on a stopped
driver; the direct path's core entries now gate on the same flag, and
a driver that comes up running is marked started at init.

samples/audio/stopped_submit drives the int16, float and per-sample
entries through a device that records its running state at each
write, on the direct pipeline with audio sync on.
2026-10-23 12:32:07 +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
libretroadmin d14a29edff gfx: keep shared shader blobs and the X11 icon once
The Vulkan driver, the glcore driver and their slang filter chains each
embedded their own copy of the same SPIR-V: the menu pipelines, the
alpha-blend, font, mesh, stereo and stock-rect shaders, the slang
opaque pair and the HDR composite, some of them twice in one file.
gfx/common/spirv_shaders.c now holds each one once, built when a
driver that takes it is, and the drivers use it from there.

The two X11 contexts likewise carried the same window icon; it lives in
x11_common.c. rvp9 kept the ADST_ADST scan and neighbour tables below
32x32 as copies of the DCT_DCT ones and now returns those.
2026-10-23 11:46:16 +02:00
Codex 011922e32a Connect Android recording GPU and output ownership
Service fixed capture slots through GLES drawing, read-retirement fences and encoder Surface posts. Match native admission/configuration and reported encoder color metadata, and own fresh output descriptors without overwriting existing files. Keep the backend unregistered until lifecycle/executor integration and native device output acceptance.
2026-10-23 10:05:01 +02:00
LibretroAdmin 9f36dcdc0e CI MSYS2: retry a corrupt package from the next mirror
A package that fails its checksum is deleted and fetched again from the
same first mirror, so every retry failed the same way. Each retry now
moves the first server of every mirror list to the end.
2026-10-23 09:33:55 +02:00
Codex 1da91e4769 Add fixed Android GPU recording capture owners
Own encoder EGL surfaces, fixed RGB hardware buffers, Vulkan copy submissions and GLES imports. Keep source-copy completion separate from worker GPU-read retirement, preserve readiness-fd ownership and refuse reuse without completion proof. These helpers remain unregistered pending drawing/controller integration and device output acceptance.
2026-10-23 08:50:17 +02:00
Codex 633b02914e Add worker-owned Android encoding and MP4 mux foundations
Admit complete hardware Surface encoder formats and own bounded video/AAC encoder output, partial PCM input and native MP4 muxing on the recording worker. Preserve sample-clock gaps and explicit source end timestamps across backpressure and drain.

Exercise native API faults, real JNI admission and PCM queue ownership on Windows and Linux, and compile the combined recording helpers across Android ABIs and file-offset modes. These helpers are not yet registered as a recording driver; GPU Surface submission and device file acceptance remain separate work.
2026-10-23 07:14:16 +02:00
LibretroAdmin 0db3f82f64 rslc: SPIR-V ingest, validating every cache hit
rslc_compile_spirv() reads a module this compiler or glslang emitted
back to its reflection: the two blocks and their members, samplers,
stage inputs and outputs, and the block members reached from the
entry point, laid out by the same writer the GLSL path uses.  The
module's decorated offsets are held against that layout; a module
that differs, carries a capability beyond Shader, has unstructured
control flow, is for another stage or is malformed is refused.
Bodies are not decompiled: a cached module serves the Vulkan driver
and the reflection tables, and a textual driver prints again from
the GLSL.

slang_process() passes each SPIR-V cache hit through the ingest when
rslc is the compiler (a refusal is a miss and the stage recompiles)
and prints the same text for a hit as for a miss.

rslc_reflect_check --ingest feeds glslang's corpus modules through
the reader and holds the tables against SPIRV-Cross on the same
words; rslc_image_check.sh -c captures the rslc run again on its
warm cache.
2026-10-23 06:59:28 +02:00
LibretroAdmin 3aca0fdf94 gl3_shader_status: stub glslang_compile_shader_text 2026-10-23 06:11:01 +02:00
LibretroAdmin f8f1673c5b rslc: MSL printer, taken by the Metal driver
rslc_emit.c gains the MSL skin: blocks as structs at their std140 /
std430 offsets at buffer 0 and 1, textures and samplers at the
binding, stage_in / return structs with Metal's attributes, and,
Metal having no globals, every function taking what it reaches by
reference through a fixed point over the call graph, with main0
holding the stage's variables.  Arrays are metal::array, vector
components passed to out parameters go through temporaries, and
names C++ or Metal reserve are suffixed.  slang_process() hands
rslc's MSL to newLibraryWithSource in place of SPIRV-Cross's when
rslc compiled the stage.

tools/rslc-msl-check.py type-checks every corpus stage with clang++
against tools/rslc-msl-shim, a stand-in metal_stdlib, and holds the
interface against the GLSL source; xcrun metal over the kept files
is the run of record on a Mac.
2026-10-23 06:03:46 +02:00
LibretroAdmin 07b1552c73 rslc: HLSL printer, taken by the D3D drivers
rslc_emit.c gains the HLSL skin (shader model 40 and 50): matrices
spelled transposed so cbuffer members overlay std140 and products
print as mul() with swapped operands, the uniform block at b0 and
the push block at b1 with packoffsets, Texture2D + SamplerState
pairs at the binding, statics behind a wrapper main with TEXCOORDn /
SV_Target / SV_Position semantics, aggregate constructors as
initialiser lists or hoisted temporaries, constant aggregates at file
scope, helpers for mod, inverse, pack/unpack and bitfield ops.
slang_process() hands rslc's text to D3DCompile in place of
SPIRV-Cross's when rslc compiled the stage; a stage slang_rect
rewrites keeps crossing the rewritten SPIR-V, on glcore too.

tools/rslc-hlsl-check.py compiles every corpus stage at both models
with a real D3DCompile (tools/rslc-hlslc.c under Wine or on Windows)
next to SPIRV-Cross's HLSL of the same stage; rslc_image_check.sh -w
runs the D3D image gate on a MinGW RetroArch under Wine.
2026-10-23 05:30:24 +02:00
Codex 1b1593333f Preserve explicit chroma placement through Metal frame conversion
Carry placement in private source/view leases and reconstruct explicit vertical/top-left chroma with bounded taps. Keep unspecified sampling and core HDR ABI unchanged. Extend native PQ/HLG pixel acceptance across all three placements.
2026-10-23 05:22:21 +02:00
Codex 9733e9f7bd Terminate generated Vulkan oracle headers with a newline
Clang rejects freshly compiled shader initializers under the strict C89 oracle flags when the generated include has no final newline. Write an explicit LF on every host while keeping the shader payload and pixel assertions intact.
2026-10-23 04:42:18 +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 9033b5ef9d configuration: keep the generated setting rows in tables
The bool, float, uint and int rows the settings/ def files generate
were each added to their list by an inline block of code. They are
now static tables of key, settings_t offset and default, copied into
the same lists, in the same place, by one loop per kind. With the
menu a key is stored as its label's offset in the packed label blob,
so the tables carry no pointers to relocate.

microphone_block_frames had a configuration row on every platform
but its label only on mobile; it is registered by hand off mobile.
The capacity check and settings_check.py count the table rows, and
the harnesses that include configuration.c stub RARCH_ERR, which the
copy loop's full-list report now reaches.
2026-10-23 03:16:21 +02:00
libretroadmin 6868062dc3 exynos: apply a core's rotation, mirror overlay images
The G2D calls libdrm offers cannot rotate, so a core's rotation is now
applied by the CPU as the frame is copied into a GEM buffer of its own,
and the turned frame is placed at its turned size. Views are drawn as
one frame while a rotation is set; the menu stays unturned, as in the
other drivers.

They cannot mirror either: an overlay image whose quad runs opposite
to its texels is written mirrored into its buffer when that changes,
and drawn from there. exynos_g2d_test checks all three turns and the
mirrored quads.
2026-10-23 03:11:33 +02:00
libretroadmin fbb4548c7c exynos: read frames from the core's memory, blend the input overlay
Where the G2D sits behind an IOMMU, it reads a core's frame from the
core's own memory as userptr, and the frame is not copied. The kernel
keeps a mapping per address and size and syncs the caches only when it
makes one, reusing it unsynced after that, so each frame gives its
address another size and is mapped afresh. Memory the kernel will not
map is copied from then on, as is a frame that leaves no room on its
last page for a second size. Without an IOMMU frames are copied as
before.

The input overlay's images each get a GEM buffer, premultiplied, with
their alpha applied when it changes, and the G2D scales and blends
them over the page, behind the menu or over it. Where an image lies
outside the frame, that area is cleared first so it is not blended
over what it left there on that page. exynos_g2d_test models the
kernel's reused mappings and checks both; the overlay setter check now
reads this driver, and the compile lane builds its overlay code.
2026-10-23 02:57:42 +02:00
LibretroAdmin 552ef9116f rslc: GLSL and GLSL ES printer, linked directly by glcore
TU 3 prints the IR as GLSL at the version the caller names, desktop
or ES, with the interface named as the caller asks (opts->names):
attributes, varyings and samplers by prefix and location or binding,
the uniform block as a std140 block, the push block as a struct with
one uniform of it since GLSL has no push constants.  It prints what
the SPIR-V emitter encodes - constants already folded in double so
the GL compiler cannot fold them again in its own precision, only the
functions main reaches, scalar swizzles as splats, const only on a
constant initialiser, integer varyings flat, noperspective left out
of ES - and asks for GL_ARB_texture_gather, GL_ARB_gpu_shader5 and
GL_ARB_shading_language_packing where a version below 400 or 420
needs them.  Reachability, the reflection section and the blob
around a payload move out of the SPIR-V emitter so both share them.

glslang_compile_shader_text() takes what a driver that compiles
source wants (struct slang_text_target) and, when rslc is the
compiler, prints each stage beside its SPIR-V; glcore asks for GLSL
at the context's version with its RARCH_* names and links that text
in place of crossing the SPIR-V, through the same attribute, block,
push and sampler lookups as before.  The SPIR-V cache brings words
only, so a hit still crosses; the task graph makes the blob the
cached unit later.

tools/rslc-glsl-check.py prints every corpus stage at 330 and 300 es
(and 450 / 320) and has glslangValidator accept it: 6463 of 6463
accepted at 330 / 300 es, 6537 of 6537 at 450 / 320; the 75 stages a
version itself cannot express (arrays of arrays, textureGather with
a component below 400) are rejected with SPIRV-Cross's output as
well and are counted apart.  The image gate on glcore (lavapipe,
GL_ARB_gl_spirv kept out of the way so the printed GLSL is what
runs) is byte-identical to the glslang build on stock, bilinear,
nearest, zfast-crt, crt-geom, crt-easymode, crt-guest-advanced,
crt-royale, lcd-grid-v2, agb001, fxaa, grade, ntsc-adaptive,
xbrz-freescale-multipass and scalefx.
2026-10-23 02:40:40 +02:00
libretroadmin 2a782ed953 ci: every runner job installs its packages through the apt cache
Each job lists its Ubuntu packages in APT_PKGS and installs them once,
early, with apt-install.sh: a bounded fetch with the main archive as
fallback, and on later runs an install from the actions cache with no
network at all. The installs scattered through later steps fold into
that list. The cache is keyed by package set and runner image, so jobs
asking for the same packages share one entry, and apt-install.sh takes
--no-install-recommends for the jobs that relied on it.

tools/ci_apt_check.py, run by the CI runner workflow, fails any other
apt-get in a runner job; container jobs keep their image's own bounded
installs. tools/ci_local.py skips the cache restore and save steps as
it does actions/cache, so the converted jobs still run locally.
2026-10-23 02:09:40 +02:00
libretroadmin 2d31684683 exynos: aspect, zero-copy frames, dirty-rect text, mode switches
The frame was placed at its pixel aspect: a 256x224 frame on 1280x720
filled 822x720 rather than the 960x720 4:3 the core asks for. It now
takes the core's aspect, the one chosen in the menu, and the CRT
switch's, and is placed again whenever they change.

A core that renders into the frontend's buffer is lent the GEM buffer
the G2D reads the frame from, so its frame is never copied; a core
that reads its frame back is refused, as the mapping may be uncached.
The cached frame is retired before that buffer is replaced.

With vsync off, a frame that came before the last flip finished
neither waited for it nor queued a flip behind it: the driver blocked
on the flip every frame. It now drops that frame and returns.

Messages and the statistics, which the driver did not draw, get a
buffer of their own rather than the menu's. Only the area the glyphs
cover is cleared and blended, where the frame's info puts them, in
its colour; the font follows video_font_enable, _path and _size.

The menu's alpha is applied as it is copied. Screenshots read the
page on screen. A mode switch and a CRT modeline build new pages at
the new size, the old ones removed only once the new ones are up;
video_fullscreen_x/y pick a mode at the refresh rate. The display
server is told it is on KMS, and of the mode.

The framebuffers were removed with the GEM handle as the fd, so none
were; init no longer leaks the card when no display is found, nor
crashes on a card that reports no version.

samples/gfx/exynos_g2d runs the driver against a fake kernel and a
G2D that draws into the GEM buffers, in the samples/gfx workflow; its
23 checks fail on the driver before this. The driver reads no live
settings from the video thread any more, so its exemption in
video_thread_state_check goes, and the doc drops the forked libdrm
and kernel it no longer needs.
2026-10-23 01:51:17 +02:00
LibretroAdmin 1f72e4abcc slang: compile with rslc behind video_shader_compiler, image and reflection gates
slang_compile_stage() builds each stage with rslc or glslang: a build
with one compiler uses it, a build with both follows the config-only
video_shader_compiler (0 auto, 1 glslang, 2 rslc), where auto is
glslang on every driver until that driver's rslc corpus gate is met.
A stage rslc rejects falls back to glslang, and the SPIR-V cache names
rslc entries apart from glslang's so neither answers for the other;
glslang's names are unchanged, so existing caches stay hits.

rslc folds constants as glslang does, so the module is the same
module: literals keep their double value, constant expressions fold
in double and round once, the math builtins, matrices, transpose and
determinant fold (inverse does not, as in glslang), a member or
element of a const aggregate folds to the initialiser's argument, a
local const is its constant and leaves nothing behind when unused, a
global const is emitted either way, and among implicit conversions
the overload converting the fewest arguments wins.  Before this fxaa
differed by one ulp in 0.587/0.299 and the thresholds it feeds flipped
on edge pixels.  The reflection section marks the block members the
stage reads, as SPIRV-Cross's active buffer ranges do.

samples/gfx/rslc_image: a deterministic pattern core and a script
that captures the same frame through a preset under each compiler
and compares the PNGs byte for byte; on lavapipe under Xvfb stock,
bilinear, nearest, zfast-crt, crt-geom, crt-easymode, crt-guest-
advanced, crt-royale, lcd-grid-v2, agb001, fxaa, grade, ntsc-adaptive,
xbrz-freescale-multipass and scalefx are identical.
samples/gfx/rslc_reflect holds the blob's tables against SPIRV-Cross
on the same module over the corpus: 3269 of 3269 stages agree.
tools/rslc-corpus-check.py now also compares the float constants a
module uses: 3269 of 3269 identical.
2026-10-23 01:29:32 +02:00
Codex 19543fcd65 vulkan: collect completed deferred Android caches
Allow owner completion polling and cold collection after fence proof. Keep pending and failed work retained until device destruction.
2026-10-23 00:34:42 +02:00
libretroadmin 426a09988b menu: set setting actions through one function per field
SETTINGS_ACTION_SET expanded to a copy of the actions tuple and an
interning lookup at each of its 240 call sites, and compilers inline
that lookup into every one of them. Each field now has a small static
setter the macro calls, so a site is a single call. menu_setting.c
loses 62 KB of code at -O3 and 7 KB at -Os on x86-64, most of it from
the cheat and input builders, and the settings it builds are
unchanged.
2026-10-23 00:32:28 +02:00
libretroadmin efe0797f15 menu: store descriptor handlers as indices
A settings descriptor row named its handlers through seven pointer
fields, almost all of them NULL. Each row now carries a one-byte index
per handler into a shared table, generated together with its SDX_*
index names from menu/menu_setting_handlers.h; a handler a row names
but the list lacks fails to compile, and each entry is checked against
its slot's type. The label ids and flags are stored at their real
16-bit widths. A row shrinks from 128 to 72 bytes on 64-bit targets
and from 88 to 60 on 32-bit ones.

A row's handlers are now set as one actions tuple interned once,
rather than one interning lookup per handler. The settings built from
the rows are unchanged.
2026-10-23 00:32:24 +02:00
LibretroAdmin a159f9ceec rslc: SPIR-V emitter, interface identical to glslang over the corpus
TU 2 now writes SPIR-V 1.0 from the IR: interned types and constants,
glslang's layouts (std140 UBO, std430 push unless declared std140),
gl_PerVertex for vertex stages, builtins emitted on first use, only
functions reachable from main, structured CFG with short-circuit
phi and OpSelect, constant folding, samplers passed by pointer, and
the reflection section of the blob with block sizes as SPIRV-Cross
reports them so push and UBO sizes stay byte-identical.

Every stage glslang accepts (3262) passes spirv-val for vulkan1.0 and
round-trips through SPIRV-Cross; tools/rslc-corpus-check.py reports
the shader interface (variables, Location/Binding/DescriptorSet/
BuiltIn/Block/interpolation/RelaxedPrecision, member offsets and
strides, capabilities, execution modes) identical to glslang's module
on all 3269 stages in the dump.  rslc_test gains --spirv and a
reflection test.  Default arena cap is 16 MiB: the nnedi3 nns256
stages (1 MB of SPIR-V) need 10 MB.

Front end: a prototype after a definition no longer replaces the
body's parameters; std430 no longer sets the std140 flag; samplers
take no float default precision; builtins take the default precision
as glslang applies it.
2026-10-23 00:26:09 +02:00
Codex 325e88902d vulkan: add warm Android submission and cache retirement
Duplicate borrowed ready fds for binary semaphore import and return image leases only after exported completion or fence proof. Keep warm submission/cache hits free of resource creation and waits. Retain failed or pending work through deferred device destruction. Exercise FD ownership on POSIX and Windows CRT pipe models.
2026-10-23 00:05:07 +02:00
libretroadmin 57bde60076 ci: the builtin media player job installs through the apt cache, bounded
A plain apt-get install stalled on the runner's mirror held the job
until its 15 minutes ran out. It now installs through apt-install.sh:
each download bounded, the main archive tried when the mirror stalls,
and the packages from the job's apt cache when warm.
2026-10-22 23:56:35 +02:00
Codex abbc44ca65 vulkan: add Android import resources and RGB conversion
Prepare frontend-owned YCbCr device admission, AHardwareBuffer image resources and immutable-sampler conversion pipelines. Keep foreign ownership transitions explicit and resource creation cold. Validate cropped SDR, linear FP16, PQ and HLG pixels using embedded and freshly compiled shaders. Native Android buffer and fence acceptance remains separate.
2026-10-22 23:54:04 +02:00
Codex 1d532072b7 android: export color-resolved hardware frame leases
Intersect coded, container and native color metadata before transferring image ownership. Preserve HDR mastering/light metadata and independent PQ/HLG leases, refuse unresolved or conflicting metadata, and validate HEVC/VP9/AV1 bit depth without adding a transfer or tone-mapping pass.
2026-10-22 23:44:02 +02:00
Codex c6bb11a7b3 android: bridge image tickets to renderer frame leases
Export bounded opaque leases with generation-checked claim, retry and completion. Preserve resolved color and container HDR metadata while refusing native CICP conflicts. Keep acquired images alive through renderer ownership and GPU fence return, with no frame-path allocation or wait.
2026-10-22 23:40:10 +02:00
Codex ce440eaa20 android: add admitted MediaCodec worker sessions
Create the explicitly selected hardware component and verify its identity before configuration. Submit compressed access units and pump Surface output with zero dequeue timeouts. Retain format crop/color metadata, preserve presentation timestamps and handle terminal output without CPU pixel readback.
2026-10-22 23:35:34 +02:00
Codex 6704a6fe15 android: add fence-aware image reader ownership
Retain native images and reader libraries until ready and completion fences retire. Publish bounded generation-checked renderer returns without waiting or allocating. Load NDK image APIs at runtime, refuse malformed images and reject success without a native image. Exercise Linux fence and Windows lifecycle contracts.
2026-10-22 23:31:02 +02:00
LibretroAdmin cad22bc8f8 rslc: parser and typecheck into the IR, whole corpus accepted
The front end now parses an expanded stage into the structured IR:
interned types, variables, overloaded user functions, the uniform and
push blocks, samplers and stage I/O, and every expression and
statement form the corpus uses, typed as it is parsed. Implicit
conversions are explicit CONVERT nodes, builtin calls resolve against
a signature table, constant expressions fold for array sizes, case
labels and layout values, precision propagates through expressions as
glslang's does under Vulkan rules, and integer samplers return
integer texels. Numbers lex by the GLSL rule, so 1.0.xxx is a literal
and a swizzle.

Over the corpus dump every one of the 3262 stages glslang compiles
parses and typechecks, and every stage it rejects is rejected for the
same reason. The stock vertex and fragment IR match the fixtures in
the sample test. The size reference moves to the new measurements.
2026-10-22 23:30:26 +02:00
LibretroAdmin ba90d5e9fe rslc: preprocessor and lexer, identical to glslang over the corpus
The front end now lexes a stage and runs the GLSL preprocessor over
it: object-like and function-like macros with prescanned arguments and
## paste, #if / #elif with constant expressions and defined(), #ifdef,
#ifndef, #else, #endif, #undef, #version, #extension, #line N "file",
#error, #pragma ignored, line continuations, and glslang's reading of
"##else" inside an inactive branch. The output is a chunked stream of
16-byte tokens; everything an expansion builds on the way is scratch,
released when it has drained, so the heaviest corpus shaders cost
their tokens and nothing else.

Symbols are stable records with the keywords seeded at init; tokens
refer to them by index and to their spelling by source offset.

tools/rslc-pp-check.py compares the token stream and line numbers
against glslangValidator -E over a corpus dump: 3282 of 3282 stages
identical. The size reference moves to the new units' measurements.
2026-10-22 23:30:16 +02:00
Codex 7bc0eda2c6 android: add explicit hardware codec inventory query
Select a non-alias hardware codec by MIME, dimensions and Android profile before cold session admission. Preserve incoming JNI exceptions, bound local references and decline unsupported or contradictory inventory. Validate using real JNI with synthetic Android classes on Windows and Linux.
2026-10-22 23:24:59 +02:00
Codex 3411fe45e8 av1: add configuration-aware backend admission
Preserve the published backend prefix and legacy factories while allowing sequence and color metadata at cold setup. Reject invalid packet-drain combinations and close partial failures. Withdraw Vulkan and D3D11 offers conditionally so shutdown cannot erase a replacement.
2026-10-22 22:52:36 +02:00
Codex 4f5ae7450c media: add explicit play and pause actions 2026-10-22 22:33:14 +02:00
libretroadmin f4dafebb39 materialui: convert theme colours from a table
materialui_prepare_colors() walks one small table of theme field to
colour slot offsets instead of expanding thirty inlined conversions,
which leaves the colour state byte-identical while the function is a
fraction of its former size and runs faster.
2026-10-22 22:17:08 +02:00
libretroadmin 0c23d6aa48 menu: shrink the sublabel, title and settings descriptor tables
The sublabel and title maps store their enum ids as msg_hash_id_t,
which the MSG_LAST range check already covers, halving the sublabel
rows and taking title rows from 12 to 6 bytes on every ABI. The
sublabel map is kept sorted by label value and looked up by binary
search instead of a linear scan; tools/sublabel_map_check.py enforces
the order in the msg_hash CI workflow, since a misplaced row would
silently lose its sublabel.

setting_desc_t carries def_offset in the hole after step, so a
descriptor row is 128 bytes instead of 136 on 64-bit targets and
unchanged on 32-bit ones. Every row builder supplies the slot in its
new position; the rows' field values are unchanged.
2026-10-22 22:17:04 +02:00
LibretroAdmin 57d7821532 rslc: size the output buffer from a hint, count growth
The backend sizes the blob up front from the IR, so the print buffer
takes prefix + hint bytes in one allocation and the blob is that
buffer. Doubling remains for a misestimate and is counted in grows, so
the corpus check can report a hint that was wrong.
2026-10-22 22:13:31 +02:00
Codex dd480a1d00 vp9: carry container chroma through hardware admission
Append config-aware admission after the published backend prefix and validate coded/container color before setup. Parse Matroska chroma without truncation; map left/half-row siting to VPCC 0 and top-left to VPCC 1, rejecting unrepresentable horizontal-half layouts. Preserve legacy callback selection, resolved source color and software fallback. Cover independent FFmpeg-generated locations, metadata conflicts and ownership.
2026-10-22 22:10:46 +02:00
LibretroAdmin a50d6351c3 AGENTS.md: never commit build output
Harness executables are build output like any other; the rule says
how to stage so one cannot slip in, and that a new harness ignores its
own binary in the commit that adds it.
2026-10-22 22:09:56 +02:00
LibretroAdmin cdf419d99c samples: do not track the rslc_corpus_dump executable
The harness binary is build output, like every other sample's; it is
removed from the tree and ignored alongside them.
2026-10-22 22:05:09 +02:00
LibretroAdmin e06e531747 slang: corpus dump harness and census for the rslc dialect
glslang_build_stage_source() moves from slang_process.c to
glslang_util.c and is exported: reading, include expansion and the
stage split are one unit, usable without the SPIRV-Cross side.

samples/gfx/rslc_corpus/rslc_corpus_dump takes .slang files through
that unit and the vendored glslang via glslang_compile_spirv, writing
each stage's GLSL and SPIR-V and a manifest of what compiles.
tools/rslc-census.py turns a dump into a per-construct table at the
source and the SPIR-V level, so the rslc dialect is the measured union
of libretro/slang-shaders. census.md is the result for the corpus as
of today: 1631 of 1653 shaders compile, every one of them #version
450, capabilities Shader and ImageQuery only, GLSL.std.450 the only
extended set, combined samplers throughout.
2026-10-22 22:02:32 +02:00
LibretroAdmin 82263a0467 libretro-common: move rslc to gfx/shaders/
gfx/shaders/ is the shader-language toolchain directory, laid out like
gfx/scaler/ and gfx/math/: sources in gfx/shaders/, the public header
at include/gfx/shaders/rslc.h, the sample in samples/gfx/rslc. rslc is
its first unit; the Cg/HLSL transform inside the D3D9 driver is the
next candidate.
2026-10-22 22:02:32 +02:00
Codex 33e3ed364f vp9: withdraw only the retiring hardware backend
Compare the registered offer before clearing it so stale renderer shutdown cannot unregister a replacement. Use the conditional operation in Vulkan and D3D11 retirement. Preserve existing close snapshots and add registry replacement, repeated withdrawal and subsequent-open coverage.
2026-10-22 21:50:10 +02:00
Codex e713a2d65d media: drain terminal VP9 packets before WebM EOF
Preserve the final visible picture across trailing hidden packets and poll packet-decoder completion only after confirmed demux EOF. Keep exported leases independent, retain surface backend behavior and append the cold drain callback after the published backend prefix. Cover delayed terminal output, repeated pending polls, seek/close and missing-input boundaries without activating a native backend.
2026-10-22 21:45:01 +02:00
Codex a8a7ba78c3 media: pass WebM color metadata into VP9 admission
Append color-aware admission after the published backend prefix. Let factories inspect borrowed container metadata before native startup, preserve legacy factories and close snapshots, and clean up refused or partial opens without disabling software playback. Verify prefix offsets and metadata/ownership/fallback behavior with C89 fixtures.
2026-10-22 21:38:50 +02:00
LibretroAdmin 532a5229de libretro-common: add rslc, a C89 slang compiler (phase 0)
rslc compiles an include-expanded Vulkan GLSL stage into one
structured IR and serves SPIR-V, GLSL, GLSL ES, HLSL, MSL or the
reflection tables from it, with no file I/O, no locks and no process
state, so RetroArch and cores can both vendor it. This commit lands
the ABI (rslc.h), the shared arena, intern table, print buffer and
error sink, the blob format with its validating zero-copy reader, the
three translation units with their entry points, the griffin
amalgamation, HAVE_RSLC in configure and the Makefiles, a C89 sample
test and tools/rslc-size-budget.sh. The front end, emitters and
SPIR-V ingest report not implemented until the next phases fill them;
nothing calls rslc from the slang path yet.
2026-10-22 21:37:51 +02:00
LibretroAdmin 30efdd3f74 deps/glslang: keep only the sources RetroArch compiles
The tree is now the include closure of the units Makefile.common and
griffin_glslang.cpp compile: 85 files. The standalone tools, gtests,
the Test corpus, the HLSL front end, the SPIR-V disassembler and
remapper and the upstream build scripts are removed, and
ShaderLang.cpp and intermediate.h carry no ENABLE_HLSL branches. This
is the set the rslc replacement is measured against.
2026-10-22 21:37:51 +02:00
Codex 91a811ebd6 media: preserve VP9 repeat packets for opaque decoders
Add an explicit packet-decoder factory mode so opaque codecs receive show-existing packets without committing new DPB state. Retain output-only repeats for surface backends. Cover hidden references, repeat backpressure and cancellation, WebM superframe/lacing dispatch and output surviving close.
2026-10-22 21:27:38 +02:00
libretroadmin 6472c44cd8 samples: ffmpeg_pipeline links recording_deinit as it now is
recording_deinit cancels a native GPU recording still starting and
asks whether the driver is a native GPU one, so the fragment the test
takes from record_driver.c named recording_gpu_cancel_start,
record_nvenc and record_metal, which it did not build. The test
defines them, and a lane stops a recording whose GPU start is pending:
cancelled, with nothing finalized, freed or pushed to history.
2026-10-22 21:27:07 +02:00
Codex 34095c5ef5 h264: remove unused variadic dispatch macro
The bit-depth wrappers already call the typed C89 functions directly. Remove the unused RH264_K2 definition and undef so Apple Clang can compile the pipeline support objects with strict C89 warnings as errors. No decoder call sites or generated code change.
2026-10-22 21:25:32 +02:00
Codex 23af0ad7b4 vp8: bound picture-header setup stack usage
Keep the inherited probability/reference snapshot in a separate non-inlined reset helper, outside picture-header parser temporaries. Preserve the same state and arena without new allocation. Apple Clang reported a 2240-byte rvp8_begin frame under the 2048-byte LRC budget. GCC/Clang sanitizer pixel comparisons across repeated key/inter frames and four ABI stack builds pass; native acceptance remains required.
2026-10-22 21:24:27 +02:00
Codex 697bf37a37 mp4: resolve VP9 configuration and coded color metadata
Interpret retained vpcC before decoder setup and reconcile coded profile, depth, subsampling and range with container color. Preserve mastering and light metadata across rewind, and refuse contradictory descriptions. Add portable metadata and configuration ownership regressions.
2026-10-22 21:16:09 +02:00
Codex c3925d9258 vp9: reconcile codec and container color descriptions
Provide shared C89 color reconciliation for VP9 configuration records and coded headers. Reject contradictory range/primaries/matrix descriptions while preserving explicit transfer and mastering/light metadata. Add portable ownership, aliasing and conflict coverage.
2026-10-22 21:15:55 +02:00
Codex 01f750344a queues: bound POSIX SPSC shared-memory name formatting
Use snprintf and reject formatting failures before shm_open. This keeps the shared-memory setup branch buildable with modern Apple SDK deprecation warnings treated as errors.
2026-10-22 21:09:37 +02:00
libretroadmin 66bcbddc44 ci: the unclaimed samples install mingw-w64 and Wine up front, bounded
The job installed mingw-w64 and Wine inside its loop, when the first
sample cross-built for Windows turned up, with apt-get quiet and
unbounded: a mirror that stalled there held the job without a line of
output until its 30 minutes ran out, where it takes 14. They are now
installed up front through apt-install.sh, every download bounded,
the main archive tried when the mirror stalls, and from the job's apt
cache when warm.
2026-10-22 20:57:02 +02:00
Codex 6eb58d3121 av1: submit repeated display to opaque packet decoders
Snapshot an explicit packet-decoder mode at stream admission. Submit show-existing header OBUs before committing reference refresh, preserving immutable BUSY retries, cancellation and independent output leases. Existing surface factories keep output-only repeats.

Exercise both modes against generated 8/10-bit reordered and grain streams, including invalid repeats, and verify packet acceptance before keyframe reference refresh.
2026-10-22 20:49:21 +02:00
libretroadmin eb7335f939 ci: every samples/gfx job installs through the apt cache, vkms keeps its kernel
When the runner's mirror stalled, a plain apt-get install hung until
the job's time ran out: the vkms job's install was cancelled at 20
minutes, and slang-last-pass-target and metal-frame-lease failed or
were cancelled at 10. Every job in the workflow now installs through
apt-install.sh: each download bounded, the main archive tried when the
mirror stalls, and the packages from the job's apt cache when warm.

The vkms job keeps the kernel it boots in a cache entry keyed on its
version, so the 180 MB it needs are fetched once per kernel, and
run_vm.sh fetches it under a bound and retries a stalled download.
2026-10-22 20:45:59 +02:00
Codex 1bee5b80b2 vp9: bound reconstruction and probability decoder stacks
Keep predictor and coefficient-band expansion out of reconstruction and header frames. Reuse inverse-transform scratch after its previous use has ended for coefficient token decoding, without changing decoder size or allocating per frame.

Check whole-decoder C89 stack budgets with and without workers, compile both Android ABIs, and compare 8/10-bit cropped and tiled output against FFmpeg.
2026-10-22 20:36:45 +02:00
Codex 1e119cbe07 vp9: parse container codec configuration records
Validate vpcC FullBox bounds and profile, depth, chroma and level constraints while preserving raw CICP values for later admission. Register the portable configuration regression in the common sample runner.
2026-10-22 20:32:16 +02:00
Codex 82b9ef7a7b mp4: preserve VP8 and VP9 codec configuration metadata
Retain the complete vpcC box for later color and chroma admission even when codecInitializationData is empty. Cover configuration ownership after input release and allocation failure for both sample entries.
2026-10-22 20:30:47 +02:00
libretroadmin e0698da9aa ci: samples/gfx in two jobs, its packages from an apt cache
The samples/gfx job ran out of its 30 minutes with gfx_thumbnail_preview
(5 to 9 minutes on its own) and a package install that took 7.5 on a
slow mirror. gfx_thumbnail_preview becomes a job of its own, running
beside it.

Both install through apt-install.sh, which keeps the package lists and
the .deb files a job fetched in an actions/cache entry keyed on the
job, the runner image and the package list: warm, nothing is fetched
and only the unpacking is left; cold, or when the cached lists no
longer resolve, it refreshes, fetches with the stalled-mirror fallback
the job had, and the entry is saved again. A later plain apt-get
install in the job resolves from the same lists, and every package
samples/gfx installs in its steps is in its list.
2026-10-22 20:15:55 +02:00
libretroadmin 52473601f5 drm: place the plane in one commit, and run the driver on vkms in CI
The plane's turn was set on its own before the plane was placed: a
kernel checks that against the plane's old place, which a quarter
turn of a frame that fills the screen does not fit, so on vkms - and
any display that cannot scale - a core turned 90 or 270 degrees got
no picture. With atomic commits the buffer, the place and the turn
now go in one commit. And a frame placed otherwise in a frame memory
buffer the plane still shows removed the framebuffer under it, which
turns the plane off; such a frame is copied instead.

drm_vkms_live runs the driver on a real kernel: run_vm.sh boots
Ubuntu's HWE kernel in qemu with vkms, its overlay planes and its
writeback connector, and the test compares what the CRTC composed
with the frame - as it is, turned each way, from frame memory,
flipped without vsync, under the overlay on its own plane, at a CRT
modeline and in RGB565 - and with the screenshot readback. It runs in
CI as its own job, under ASan and UBSan.
2026-10-22 18:59:39 +02:00
Donleon a81e450c00 docs: list ArmSoM CM5 Linux/AArch64 platform (#19900) 2026-10-22 11:26:43 +02:00
libretroadmin 00f91776bc drm: drop the locks nothing takes, and build without warnings
drm_gfx made a mutex for every page, and a mutex, a second mutex and a
condition variable for the driver, from a vsync callback that is long
gone; nothing locked or waited on any of them, nor read the pages'
used flag they guarded. All of it goes, and with it rthreads. Whether
a page is free is told by the surface's current page and the flip on
its way, read on the one thread that flips.

The page loops counted with a signed index against unsigned counts.
drm_surface_lifetime now builds the driver with sign-compare as an
error and links it without rthreads.
2026-10-22 11:20:49 +02:00
libretroadmin e139f08028 drm: pass the overlay setter and video-thread state checks
overlay_setter_bounds failed: drm_gfx defines the overlay setters but
was not among the drivers it reads. It is now, and its setters name
their image index as the check reads it; each was already compared
with the page's count.

video_thread_state_check failed: the message font read the font path
from the settings on the video thread. The path is taken at init with
the rest of the video info.
2026-10-22 11:00:39 +02:00
libretroadmin b8732b6e88 drm: blend the input overlay into a core's views
Where no plane could take the input overlay, it was blended into the
frame only, so a core drawing views (a DS's two screens, say) showed
none. It is now laid out over the views' surface as over a frame,
with the plane showing them, and blended into each page of it before
the flip; the layout follows when the frame comes back.

The card is opened close-on-exec only where the C library says it
can be, so a strict C89 compile of the driver goes through.
2026-10-22 10:26:04 +02:00
libretroadmin ba0714b150 drm: draw on-screen messages
drm_gfx dropped the message every frame came with, so nothing it was
told to show reached the screen. The message is now drawn into the
page the frame was copied into, or into the views', from the font
the other software drivers use, at the size that comes out at the
configured font size once the plane has scaled the frame, made again
only when that size changes. It is laid out as it is seen, so it
reads upright on a frame the plane turns.

A frame in the threaded wrapper's memory is copied while a message is
shown rather than drawn on where it lies, and nothing is drawn under
the menu.
2026-10-22 10:26:02 +02:00
libretroadmin 803d7b4d19 drm: set CRT modelines and HDMI timings, and keep content under the menu
drm_gfx only knew the connector's own modes, so the CRT switch and
crt_switch_timings did nothing on it. The modeline the switch asks
for, and the HDMI timings in the config, are now made into modes by
drm_common, which the KMS context uses too, and drm_gfx sets them at
init and on a mode switch. The aspect the switch puts is taken up
through apply_state_changes, and the frame is placed at it with the
next frame rather than flipped to.

Content running under the menu was flipped over it every frame, the
menu flipped back after: two vblanks a frame, the two alternating. Its
frames are now written but not shown, a resize does not take the plane
from the menu, and the last frame goes up when the menu closes.
2026-10-22 10:25:58 +02:00
libretroadmin a9851be8e3 drm: scan the threaded wrapper's frames out where they lie
Under threaded video every frame was copied twice on its way to the
screen: by the wrapper into its own buffer, then by drm_gfx into a
dumb buffer. drm_gfx now gives the wrapper its frame memory as dumb
buffers, with rows at the multiple the kernel gives the narrowest dumb
buffer, and a frame written into one is flipped to through a
framebuffer made for its size, pitch and place in the buffer, kept
while those stay the same. A core rendering into the wrapper's lent
buffer reaches the screen with no copy at all.

A buffer is ready to write when the plane neither shows it nor has a
flip on its way to it, a flip done seen without waiting; one freed
while shown goes once the plane leaves it. A pitch the display
refuses, a frame the copy turns and a frame the overlay is blended
into are copied as before.
2026-10-22 10:25:54 +02:00
libretroadmin 1af9df72a4 drm: flip without waiting when vsync is off
drm_gfx's set_nonblock_state did nothing, and every commit waited for
the vblank, so fast-forward and vsync off ran no faster than the
display's refresh.

With vsync off, a frame is committed without waiting and with a flip
event; a frame that comes while the last is still on its way is not
shown, and the next one is written over it. The surfaces' third page,
made but never used, is now the one written while one is on screen
and one is on its way: the page written next is picked as neither,
rather than the other of two. With vsync on nothing changes: two pages
alternate and each commit waits, after a flip still on its way.

Anything that would change what a flip on its way reads waits for it
first: a plane update, a surface's pages going, the overlay's back
page written, the free. A card without atomic commits keeps vsync.

Placing a surface on the plane now counts the page it shows as on
screen, so the next frame is written into another, not under the scan.

drm_surface_lifetime's fake kernel takes commits that do not wait, puts
them on screen at a vblank and reports them by event; it counts
anything that touches the plane or a page under a flip.
2026-10-22 10:02:00 +02:00
libretroadmin 17db8f39bf drm: turn the frame for a core's rotation, and read it back
drm_gfx had no set_rotation, so a core asking for a quarter turn (a
vertical arcade game) was shown on its side, and no read_viewport, so
GPU screenshots and recording got nothing.

The frame plane's rotation property turns the frame where it takes the
angle: no work per frame, and the page is still lent to the core.
Where it does not, or where the input overlay is blended into the
frame (and would be turned with it), the copy into the page turns the
frame instead, writing the page in order; pages are not lent then.
The menu is shown unturned, and the plane is left unturned at exit.

viewport_info gives the frame's size as it is shown, and read_viewport
reads the page on screen back as BGR24, bottom row first, turned as
the plane shows it. Not while views are drawn.

drm_surface_lifetime gains a rotation property in its fake kernel:
the plane turning the frame, lent, and read back; a plane turning only
a quarter, where the copy makes the rest; none at all; the menu
unturned; a blended overlay upright in a frame the copy turned.
2026-10-22 10:01:56 +02:00
libretroadmin 9f612ce3da drm: set the display up as the KMS context does, and give it back
The plain DRM driver takes the first card in /dev/dri that drives a
display for the monitor index and has a plane for frames, through
drm_common, so an undriven connector gets a free CRTC. It runs the
console's mode, or the one a fullscreen size names, which the KMS
display server reports and switches through set_video_mode. A card
without atomic commits flips the plane with plane updates.

Teardown turns the plane off before its buffers go, gives the CRTC back
its framebuffer and mode, and closes the card. With no plane for frames
init fails instead of exiting, and the menu with nothing behind it turns
the plane off when it closes.
2026-10-22 10:01:52 +02:00
LibretroAdmin 0ab9c5a6c6 Bluetooth: say when the adapter is on Windows' generic driver
Windows can run a Bluetooth adapter on its own generic driver, which
often finds no classic device at all - no phone, no Wii controller -
and says nothing of it: its own pairing screen is as empty. When a
scan finds nothing, the Windows driver looks whether the adapter's
driver is bth.inf, Windows' own, and the list's "No Bluetooth Devices
Found" then says so and what to install; the log says it too. A
driver can tell this through a new, optional call.
2026-10-22 09:26:36 +02:00
LibretroAdmin 7c53700fad configuration: a Windows build's saved null Bluetooth driver is Windows' own
"null" was the only Bluetooth driver a Windows build had, so every
saved configuration says it, and the Bluetooth menu stayed hidden
until the driver was changed by hand. A saved "null" was never
chosen there: on Windows it is read as the default, Windows' own.
2026-10-22 09:26:32 +02:00
LibretroAdmin c11d3a3531 samples: the Wine smoke scenarios run side by side
They ran one after another, each mostly waiting on a window, and took
most of the Wine input job. They now run in lanes, the CPUs less one
and three at most, each with its own X display, Wine prefix and work
directory. On two CPUs the scenarios take 253 s instead of 475 s.
2026-10-22 09:05:39 +02:00
libretroadmin 9480e559a6 gl2: sample a software core's frame where it was written, on KMS
On KMS the gl driver lends a software core linear GBM buffers, each
mapped once and imported as an EGLImage, and the frame texture takes
the one the core wrote as its storage: the frame reaches the GPU with
no upload. A frame from anywhere else gets the texture's own storage
back and goes up as before. A buffer a texture holds, one a draw may
still read and the last frame's are never lent, and a smaller frame's
buffer is blanked where a larger one wrote.

Before a format is first used, two patterns written in turn through the
one mapping are sampled and read back; a driver that maps through
staging memory, or cannot sample the buffer as written, keeps uploading
for the rest of the run. Hardware cores, mipmaps, 10-bit sources and a
core that reads its frame back are not lent the buffers.
2026-10-22 09:03:29 +02:00
libretroadmin d3af23906d drm: show the input overlay on a plane of its own
The plain DRM driver takes the input overlay. Its images are composed,
whenever one of them changes, into an ARGB8888 layer covering only the
images, which an overlay plane of its own shows above the frame's:
raised by zpos where it can be, blending premultiplied, and handed each
new layer in the same atomic commit as the frame, so the overlay costs
no work on a frame where it does not change.

Where the CRTC has no plane above the frame's that takes ARGB8888, the
layer is laid out in the frame's pixels and blended into each frame's
page before its flip.
2026-10-22 09:03:12 +02:00
libretroadmin 7d8a7bed07 drm: make each framebuffer in its surface's own format
Dumb buffers are added as framebuffers with ADDFB2 and the surface's
fourcc. A legacy depth and bpp name only XRGB8888 and RGB565, so the
RGBX4444 menu surface was scanned out as RGB565.
2026-10-22 09:02:56 +02:00
LibretroAdmin ee359e3cd9 tasks: the Bluetooth scan does not spin at the end of its window
The queue runs a parked task up to half a millisecond early, so the
scan's last wait, under a millisecond, was run again at once, over
and over until the window was out. What is left within a millisecond
of the end is no longer waited.

The scan test stands in for the driver's new done call, and pins that
a driver done early ends the scan before the window.
2026-10-22 09:02:15 +02:00
libretroadmin 6153138ea9 msg_hash: pack the menu labels and point the label macros at the blob
The menu labels were the one string table still kept as an array of
pointers: 1,722 relocations, and every label also present a second
time as the literal behind its MENU_ENUM_LABEL_*_STR macro and a third
time as its configuration key. Keep them once, in a packed blob built
like the value table, and have the macros and the configuration pass
name the blob's members: a constant address, as cheap to compare as a
literal, with no copy in the string pool. A row repeated in one build
is now a duplicate member and fails to compile, which caught a stale
cloud_sync_nfs_subdir row left behind by a migration.

The rows live in intl/msg_hash_lbl_rows.h, expanded for the blob type,
the blob, the id array and the macros alike; msg_hash_lbl.h carries
its strings inline. tools/gen_lbl_str.py writes the macros from the
rows, keeping a literal where a row is guarded out or the menu is not
built, and CI checks the header is current. With the pointer encoding
gone, the lookup path loses a branch and an indirection.
2026-10-22 07:44:08 +02:00
libretroadmin 31adfe5af8 drm: scan out RGUI's own pixels, and lend it the menu page
Where the plane in use takes RGBX4444, the menu surface is RGBX4444, so
RGUI's RGBA4444 goes up row by row instead of being widened pixel by
pixel, its alpha ignored as before; the plane is asked once. RGUI is
lent the page the next flip shows when the formats match, so its
upscaled frame is drawn straight into it. The menu surface follows the
frame's size instead of cropping it to the first one's, and the plane
in use is kept when it takes the new format.
2026-10-22 07:42:53 +02:00
LibretroAdmin f78d9b9850 Bluetooth: Scan for Devices in the list, and a scan ends when it is done
The Bluetooth list had no way to look again short of leaving it and
coming back. It now ends with Scan for Devices, below the devices so
their places stay the driver's numbers for them.

A scan waited out its whole ten-second window whatever the driver was
doing. A driver can now say it has found all it will, and the scan
task, parked meanwhile and looking every 100 ms, ends then. The
Windows driver says so when its search returns, and searches for
about five seconds instead of eight: a controller in pairing mode
answers within the first. It also lists a device that has not said
its name yet, by its address, and logs a search that found nothing.
2026-10-22 07:34:56 +02:00
LibretroAdmin 2fb661c361 menu: the Bluetooth list is never left empty
A scan that found nothing cleared the list and put nothing back, and
a list with nothing in it cannot be left with Back. It now says no
devices were found, as the list does before a scan; the Windows
driver also logs how many a scan found.
2026-10-22 07:17:26 +02:00
libretroadmin 2a89bba22e drm: render into the scanout page, with no copy per frame
The plain DRM driver lends the core the page its next flip shows, so a
frame rendered there reaches the plane without being copied. A core
asking to read its frame back, or a frame whose views are composed, is
not lent the uncached page.

Frames that are copied are written at the dumb buffer's stride and read
at the pitch each frame comes with, so rows the kernel pads are no
longer sheared. A duped frame leaves the plane where it is instead of
copying from NULL, and every flip reuses one atomic request.
2026-10-22 07:14:08 +02:00
LibretroAdmin ba03395b8a Bluetooth: a Windows driver, which pairs Wii controllers too
Windows' settings often cannot pair a Wii remote or a Wii U Pro
Controller. A Bluetooth driver on Windows' own stack, through
bthprops.cpl loaded when it starts, gives the menu's Bluetooth list:
a scan finds the devices in pairing mode, and picking one pairs it.
A Wii controller is paired as Dolphin pairs one whose sync button was
pressed - the radio's address its PIN, after a pairing Windows
remembers but cannot reach is forgotten - and its HID service is
turned on, so Windows makes it a HID device the hid joypad driver
finds. Searching and pairing block for seconds, so each runs on a
thread of the driver's.

Windows builds have Bluetooth by default. The list now scans again
each time it is opened, showing the last scan's devices meanwhile, so
a pad put in pairing mode since is found; and the scan task is
declared without networking.
2026-10-22 07:08:00 +02:00
libretroadmin 07778a7f62 (KMS) Watch for hotplugs in the page flip's poll
The udev monitor is polled together with the card while the context
waits for a page flip, which it does every frame anyway, so watching
for hotplugs costs no syscall of its own. A wake on the monitor is
noted and the monitor left out of the set until check_window has read
it, so a blocking wait never spins on an unread event, and a monitor
that fails is dropped.
2026-10-22 06:48:10 +02:00
libretroadmin f76060572b net: keep the CA bundle as DER and read it in place
cacert.h carried the Mozilla roots as PEM text, which the built-in TLS
client base64-decoded into a heap copy of every certificate on first
use. Keep the bundle as the DER bytes instead: a quarter smaller in
the executable, and the trust store borrows the anchors from the
read-only data rather than decoding and copying them, which also drops
the 175 KiB of heap those copies took. The mbedTLS backend walks the
same parts certificate by certificate.

tools/cacert_split.py emits the DER form and still regenerates the
header from curl's cacert.pem or from itself; the PEM loader stays for
tests and the verify hooks.
2026-10-22 06:42:10 +02:00
libretroadmin adef83d5e7 apple: focus only windows that can take it, connect controllers found early
-makeMainWindow asserts on a window that cannot become main, and AppKit
swallows the exception inside applicationDidFinishLaunching:, leaving a
black window. Startup focuses the window actually on screen (the
borderless one in full screen, which now accepts main), and the
companion's focus hand-back asks before making a window main.

The GameController framework posts connect notices only once; a
controller it found before the joypad driver added its observers never
got a port. Init connects every controller already present; repeat
notices are ignored as before.
2026-10-22 06:24:14 +02:00
libretroadmin 713d05c341 vulkan/apple: bound the acquire fence wait, drain MoltenVK's queue at teardown
MoltenVK signals an acquire fence from the drawable's presented handler,
which macOS drops for a window hidden with presents in flight (alt-tab
away, another Space), so the unbounded fence wait froze the frame loop
for good, and the emulated mailbox thread waited on the same fence
forever, leaving the window black. On Apple the wait is bounded by the
mailbox's refresh-derived timeout and a timeout rebuilds the swapchain as
out of date; the mailbox thread reports it the same way on a new fence.

MoltenVK before 1.2.11 idles the device without what is still on its
asynchronous submission queue, so a queued present could run after the
swapchain was destroyed. Teardown on Apple drains the queue through a
fence behind it first. Other platforms are unchanged.
2026-10-22 06:24:10 +02:00
libretroadminandThomas Perrot 69d1b857a2 (KMS) Follow the display when it is unplugged
The KMS context watches its card for hotplug events through udev. When
the display in use goes away and the monitor index now picks another,
or it comes back after going, the video driver restarts on it. A
display plugged in next to the one in use leaves the output where it
is.

Co-authored-by: Thomas Perrot <[email protected]>
2026-10-22 06:23:14 +02:00
libretroadminandThomas Perrot 4c80d2aab0 (KMS) Drive a display nothing has set up yet
A connected connector with no current encoder, as with a display
plugged in after boot, now takes one of its encoders and a CRTC that is
off, and only takes a lit one when none is free. A CRTC that was off is
switched off again on restore. A requested size the display lacks falls
back to its first mode instead of failing to start.

Co-authored-by: Thomas Perrot <[email protected]>
2026-10-22 06:23:11 +02:00
libretroadmin d150b3435a msg_hash: store packed table row ids as 16-bit
Every translation header pairs its string blob with a uint32 id per
row, although no enum value reaches 16 bits. Store the ids as
msg_hash_id_t (uint16_t) instead, with a compile-time check that
MSG_LAST stays within range. The ids are only read while building the
per-language index and on the allocation-failure scan; the lookup path
is unchanged.

With 37 tables this takes about 230 KiB of read-only data out of the
executable. json2h.py emits the new type, and the packed-table check
rejects a header left at the old width so a stale regeneration cannot
slip through.
2026-10-22 06:21:19 +02:00
LibretroAdmin 4a899b3656 Input: winusb takes a pad with a handler by its ids, whatever its usage
Only HID collections that called themselves a joystick, gamepad or
multi-axis controller were looked at on Windows, so a pad whose
collection says otherwise - the Wii U Pro Controller was not seen at
all - never reached its handler. One whose ids have a handler is now
taken too, and the debug log says what each collection was taken for.
2026-10-22 06:20:14 +02:00
LibretroAdmin f244d5495a CI: the companion harnesses a job of their own, beside libretro-common's
The libretro-common job ran the unit suites and then the companion
harnesses, one after the other; it took over twenty minutes. The
harnesses now run in a job of their own, at the same time, with only
the packages they need.
2026-10-22 06:12:41 +02:00
LibretroAdmin 552789ed5a tools: ci_local says which steps a job's time went to
The three slowest steps of each job, ten seconds or more, are printed
when it ends, so a slow test is seen as one.
2026-10-22 06:12:37 +02:00
Codex 656e2a3d3d rh265: parse weighted prediction lists in coded order
Finish each reference list before reading the next, clip derived chroma offsets, and scale signed motion-compensation offsets without undefined shifts. Exercise weighted Main/Main10 B-frames against FFmpeg and make undefined-sanitizer findings fatal in the codec oracle.
2026-10-22 06:01:06 +02:00
LibretroAdmin 477b08da8a menu: Save Controller Profile only saves the profile
After writing the file it cleared the port's binds and ran the pad's
connect again to load what it had written. The pad was announced a
second time and, on the hid joypad driver, left without input until
it was plugged in again. Now the file is written and nothing else: the
binds just saved are the ones the port already has.
2026-10-22 05:33:41 +02:00
LibretroAdmin cb384dac35 Input: Xbox pads' sticks and triggers read on the hid driver on Windows
Windows gives an Xbox pad's 16-bit axes a logical maximum of -1: its
descriptor's 65535, read as signed. winusb took that range as empty
and read every stick and trigger as 0, so none could be bound. A
negative maximum over a minimum of 0 is now read unsigned, as
hid_report_desc.h already reads it.

The pad's gamepad in Windows.Gaming.Input is no longer looked for by
its ids: Windows gives every Xbox One pad's HID collection 045e:02ff
and the gamepad its own, so none ever matched. It is found by its
buttons and sticks alone.
2026-10-22 05:18:08 +02:00
LibretroAdmin 89e980a329 Audio: reopen a kept device that does not restart
A synchronous driver taken back at init whose start() fails is freed
there and init opens it afresh, so the flags follow the device that
actually opened; if that open fails too, init goes on without audio
as it does for any failed open.

pipeline_rate_change covers both: a refused restart reopened, and a
refused restart whose reopen is refused as well.
2026-10-22 05:09:10 +02:00
LibretroAdmin 7aa916e626 Input: Xbox pads' triggers and rumble through Windows.Gaming.Input
Windows' HID driver gives an Xbox pad both triggers on one axis, so
both pulled read as neither, and no rumble. On Windows 10 and later,
winusb pairs each such pad with its gamepad in Windows.Gaming.Input,
as SDL does: the one of its ids whose buttons and sticks read the
same, two alike told apart once one moves. Its triggers are then read
from there at the poll, and its rumble sent there by a thread of the
pairing's, so the frontend never waits on the pad.

combase.dll is loaded, not linked, and the process is held in the
multithreaded apartment rather than any thread's being set: before
Windows 10, or built without the header, the pad is read from its HID
collection alone, as before.
2026-10-22 04:54:22 +02:00
Codex 3dcb9a1498 test: observe thumbnail retirement without stale player access
Advance animation until the held feeder is running, and fail safely if it never starts. After reset, observe retirement through public polling and texture reclamation instead of a borrowed player pointer.
2026-10-22 04:39:33 +02:00
LibretroAdmin ea8ea6c0b6 Input: Xbox pads' triggers apart, on the hid joypad driver on Windows
Windows' HID driver gives an Xbox pad - "IG_" in its instance - both
triggers on one axis, Z, the left above its middle and the right
below. As SDL does, winusb splits them onto axes 4 and 5, each 0 at
rest, so each binds by itself. A built-in profile binds the Xbox pads'
names on the hid driver.
2026-10-22 04:31:50 +02:00
LibretroAdmin bb0968a36b Input: GameCube adapters in their PC mode, on the hid joypad driver
EVORETRO's and Mayflash's adapters on DragonRise's ids 0079:1843, 1844
and 1846 get a handler of their own, as SDL reads them: the older
firmware's report with its port first and the C stick the other way
round, the newer's a device for each port with rumble, kept up while
on. The buttons are the Wii U adapter handler's, and a built-in
profile binds them.
2026-10-22 04:05:23 +02:00
LibretroAdmin 8ee2d54931 Input: pads that pass for a DualShock 4, on the hid joypad driver
SDL's list of them goes to the DualShock 4 handler, with Sony's grip
add-on and USB wireless adaptor. As SDL, each says what it has in
feature report 0x03 - motion, light bar, rumble, its sensors' scales -
and is sent rumble and light bar together, or nothing if it does not
answer. Razer's pads do not answer but rumble; Victrix's hang if sent
anything, so are sent nothing.
2026-10-22 03:49:50 +02:00
LibretroAdmin 35c75926b8 Input: more of the pads SDL handles, on the hid joypad driver
These pads now go to the handlers that speak their protocol, each as
SDL handles it.

8BitDo:
- The Pro 2 (2dc8:6003, 6006), Pro 3 (6009), Ultimate 2 Wireless (6012),
  Ultimate 2C Wireless (301b) and Ultimate 3 (202f) join the SN30 Pro
  and SF30 Pro.
- Their back paddles and Share keep their report's numbers. L4 and R4
  from byte 10 are buttons 16 and 17.
- Rumble and motion go where the model has the mode for them:
  - feature report 0x06 for the SN30/SF30 Pro and Pro 2/3
  - feature report 0x30 for the Ultimate 3
  - the 34-byte report for the Ultimate 2 Wireless
  - none for the Ultimate 2C
- Motion is read from byte 15 at SDL's scale.

Pads that pass for a DualSense:
- These go to the DualSense handler: Victrix, HORI, Razer, NACON and
  Backbone (SDL's list).
- As in SDL, they are sent output only if feature report 0x03 says they
  take it, and give motion only if it says they have it.

Switch:
- The Switch Online SNES (057e:2017), N64 (2019) and Mega Drive (201e)
  pads go to the Pro Controller handler, which speaks their protocol.
- So do the NES and Famicom pads on the right Joy-Con's ID, by their
  names. A right Joy-Con is left alone.
- The N64 pad rests with no amplitude, as SDL found it rumbles at the
  usual rest.
- PDP's and HORI's pads that pass for a Pro Controller on their own IDs
  get its handler too.

None of the new pads has a hid autoconfig profile, so built-in ones are
added for the new 8BitDo pads, in their reports' numbering, and for
"DualSense Wireless Controller", in the RetroPad numbering the DualSense
handler gives.
2026-10-22 03:49:46 +02:00
LibretroAdmin 308da5bf99 Input: Switch 2 Pro Controller motion, on the hid joypad driver
The pad sends its accelerometer and gyroscope in every report once
woken. As SDL, the sensor clock is timed first to tell the gyroscope's
range, and the axes are turned into SDL's, then libretro's.

The gyroscope's flash biases are left out: only the bulk endpoint
reads them.
2026-10-22 03:30:59 +02:00
Codex cae7782fcb tools: recognize declared bitfields in removed-member checks
Keep named bitfields when collecting declarations, including the first field after a qualifier. Add positive and negative fixtures so still-declared PS4 reference fields do not become false dangling readers.
2026-10-22 02:58:12 +02:00
LibretroAdmin 3d0fb57731 Input: ShanWan's DualShock 3 clones and PS2-to-PS3 converter
Following SDL's PS3 driver:

- ShanWan's pads are handled as SDL handles them: the ones on their own
  ID (2563:0523), and the ones on Sony's that give a name starting
  "ShanWan". The DualShock 3 handler now takes them with no output
  report at init, because SDL found that one can make them rumble
  without stopping. Lights and rumble still go out from the first
  report on.
- ShanWan's PS2-to-PS3 converter sends a 7-byte report: the right
  stick, the left, the hat and the four face buttons, then the
  shoulders, select, start, L3 and R3. That report is now read too. Its
  buttons have no pressure, so a press gives the whole pressure axis.
- A state report with 0xFF where its buttons begin is not taken, as SDL
  found it is not a state.
2026-10-22 02:50:52 +02:00
LibretroAdmin fa433ba816 Input: motion from the hid DualShock 4, DualSense and DualShock 3
The PlayStation pads' hid handlers now give a core their motion sensors
while it asks for them, through the same handler ops as the Switch Pro
Controller.

- DualShock 4: the gyroscope and accelerometer after the triggers, in
  USB's report 0x01 and Bluetooth's full report 0x11.
- DualSense: from byte 16 of USB's report and Bluetooth's 0x31.
- Both use the nominal scale and libretro's axes, through
  sony_pad_motion.h, as winraw reads the same pads.
- DualShock 3: the accelerometer, as SDL reads it: big-endian from byte
  41, 113 counts per g around 511. Its one-axis gyroscope is not given,
  as in SDL.

input/common/pad_motion.h holds what the handlers share: a core's ask
as a mask, and SDL's axes turned into libretro's as the sdl3 joypad
driver turns them. The Switch Pro handler now uses it too.
2026-10-22 02:50:30 +02:00
LibretroAdmin 661fdde857 Input: motion from Switch Pro Controllers on the hid joypad driver
The Pro Controller, and the pads that pass for one (8BitDo's in Switch
mode among them), now give a core their accelerometer and gyroscope,
read as SDL reads them:

- The sensors' calibration is read from the pad's flash with the stick
  calibration: the factory's at 0x6020, then the user's origins at
  0x8026 where the user calibrated.
- Subcommand 0x40 turns the sensors on while a core asks for them and
  off when it is done, sent from the poll like the player lights.
- Each report 0x30 carries three samples; the newest one is kept. The
  values go out in g and radians a second, on the axes SDL gives them,
  which are the DualShock 4's, and so on the same axes as winraw's
  DualShock 4 and DualSense.

How it gets there:
- Pad handlers can now have set_sensor and sensor ops.
- hidraw and winusb route the driver's sensor calls to the slot's
  handler.
- The hid joypad driver passes the frontend's set_sensor_state and
  get_sensor_input on to its hid driver.
- The beside layer sends a hid pad's sensor calls to the hid driver
  rather than to the joypad driver it stands beside.

connect_parity_test checks the calibration reads, the 0x40 commands and
the values from a sample.
2026-10-22 02:33:45 +02:00
LibretroAdmin bec97fe243 Input: Switch Pro imitators asked for their reports on USB too
The 8BitDo SN30 Pro in Switch mode still had no input on Windows after
the greeting fix. Two more differences from SDL could keep a pad like
that silent:

- On USB the handler never asked for the full report (subcommand 0x03
  0x30). It counted on 0x80 0x04 to start reports, which a real Pro
  Controller needs. A pad that speaks the Bluetooth protocol over its
  cable sends nothing until it is asked. The handler now asks on USB
  too, first among its commands, as SDL does.
- winusb dropped, without a word, any output report longer than the
  device's own output length. The greeting goes as a 64-byte buffer;
  a pad whose descriptor has only the 49-byte command report would
  never have received it. A report that is longer only by trailing
  zeros now goes out at the device's length.

So the next log shows how far such a pad gets, verbose logging now
says, once each: the first report the handler gets, the faster link
being passed over, the reports being started, the commands being done,
and an output report winusb could not send.
2026-10-22 02:19:57 +02:00
LibretroAdmin 0a30116698 Input: the hid DualShock 4 is woken, read and rumbled as SDL does
At init the DualShock 4 handler sent a Bluetooth stack's SET_REPORT
header and report 0x11 to every driver. hidraw and winusb sent that as
report 0x52, and USB has no report 0x11 anyway, so it did nothing. Over
Bluetooth the pad never got past its short report. Over USB it never
rumbled or lit its light bar.

As SDL does, the handler now reads the calibration feature report at
init (0x02 over USB, 0x05 over Bluetooth). Over Bluetooth that read
switches the pad to its full report 0x11, which is read alongside 0x01,
with the data two bytes further on. A stack's header is skipped where
there is one. L2 and R2's travel is now read too, as axes 4 and 5.

Rumble and the light bar go out as one output report built by
sony_pad_output.h: 0x05 over USB, 0x11 with its CRC over Bluetooth.
They are sent from the poll, only when they change, and the light bar
shows the player's colour once. Nothing is sent from behind a stack's
header.
2026-10-22 02:04:51 +02:00
LibretroAdmin 2c711c6823 Input: Wii remotes on hidraw and winusb, and the Wii Remote Plus
The Wii Remote and Wii U Pro handlers were written for Bluetooth
stacks. They put the HIDP header (0xA2) in front of each output report
and, for the Wii U Pro, read their input behind 0xA1. hidraw, winusb,
libusb and wiiu take a report with its ID first, so these drivers sent
the header as report ID 0xA2. The remote never got its reporting mode,
and the Wii U Pro's buttons were read one byte off. Both handlers now
leave the header out for those drivers. A report is read whether or not
a stack's header is in front of it. The Wii U Pro only takes report
0x34, the one it asks for.

A Wii Remote Plus, or a clone that gives its name, "Nintendo
RVL-CNT-01-TR" on 057e:0330, matched neither entry for that ID and was
read from its report descriptor. It now gets the Wii handler, as SDL
does.
2026-10-22 02:04:48 +02:00
LibretroAdmin de5eae7c3f Input: the GameCube adapter is started, read and rumbled as SDL does
The adapter's reports start with output report 0x13, which is that ID
and nothing else. The handler sent 01 13, which hidraw, winusb and
libusb send as report 0x01. It now sends 13 everywhere except under
iohidmanager, which drops the first byte and sends report 0x01 itself.
Before that, it sends the set-protocol request where the driver has one
(Wii U, Wii), as SDL does for Nyko's and EVORETRO's adapters.

The sticks used to be (v - 128) * 256, so they never reached the end of
their range. They now use SDL's reach: 128 +- 88 at first, widened as a
stick goes further, and reset when a pad is put in. The L and R triggers'
travel is now read too, as axes 4 and 5, from SDL's resting point of 40.

Rumble goes as report 0x11, one byte per port, from the packet handler
and only when it changes. As in SDL, a port only gets it when its pad has
power (the second plug is in) and is not a WaveBird. There is none on
macOS, where every report goes out as 0x01.
2026-10-22 02:04:44 +02:00
LibretroAdmin 373b8c9033 Input: Switch Pro imitators get through the USB greeting
Pads that pass for a Pro Controller on USB (057e:2009), 8BitDo's among
them, do not answer 0x80 0x03, the faster-link step of the greeting.
The handler waited for that answer and counted its waits in reports,
which such a pad never sends, so it was stuck: named "Pro Controller",
with no input.

A handler can now have an idle op, which hidraw and winusb call on a
poll that brought the pad no report. The Pro Controller handler uses it
to go on by the clock, as SDL does. After 100 ms without an answer, the
faster link is passed over, other steps are tried again and then given
up on, and an abandoned greeting still sends 0x80 0x04 to start the
reports. Once the pad is ready, reports that stop on USB are started
again, which SDL also does because Steam can stop them.

Handlers without an idle op take no gate for it.
2026-10-22 01:51:06 +02:00
LibretroAdmin 3104b5b875 Input: the 8BitDo handler takes the SF30 Pro too
The SF30 Pro (2dc8:6000 on USB, 2dc8:6100 on Bluetooth) is the SN30 Pro
in another shell, with the same reports and no Home button. It now uses
the same handler under its own name, "8Bitdo SF30 Pro", which is how
its hid autoconfig profile names it, and that profile numbers its
buttons, hat and sticks the same way.
2026-10-22 01:51:03 +02:00
LibretroAdmin d2b0fe4729 samples/input/wii_handshake: rebuild when the Wii handler changes
The test builds connect_wii.c inside its own object, which make did
not know about, so a local run after a handler change used the old
object.
2026-10-22 01:50:59 +02:00
LibretroAdmin cbace6a869 Input: a hid handler for the 8BitDo SN30 Pro
The SN30 Pro in its D-input mode (2dc8:6001 on USB, 2dc8:6101 on
Bluetooth) now has a pad handler, so the hid joypad drivers on Linux
and Windows read it as its own pad rather than through its report
descriptor.

Its buttons keep the numbers its report gives them and the D-pad is
hat 0, which is how the existing hid autoconfig profile for it is
written, so that profile still matches it by name. Home also opens
the menu. The sticks are axes 0, 1, 4 and 5, as in that profile, and
L2 and R2's travel is axes 2 and 3. Both report shapes are read: the
9-byte report with no ID from firmware before 2.05, and reports 0x01,
0x03 and 0x04 from later firmware.

At init the handler reads feature report 0x06, which turns on the mode
with rumble in firmware from 2.05. When that read succeeds, rumble is
sent as output report 0x05 from the poll, once for each change.
Nothing is sent from behind a Bluetooth stack's header.

macOS keeps reading the pad from its report descriptor, because a
handler's hat would not reach iohidmanager's hat bindings.

connect_parity_test checks the handler against both reports' layouts
and its rumble report. The view check now also compares a handler's
hat with what its button op answers.
2026-10-22 01:36:51 +02:00
LibretroAdmin 7b043e77da Input: the Wii Remote handler keeps its state as a view
The remote's buttons and Classic Controller sticks are put into its view
at the end of each packet, and the getters answer from it, so the remote
is read through the snapshot bridge too. The handshake test checks the
view against what the getters read from the remote's state.
2026-10-22 01:19:48 +02:00
LibretroAdmin 4cc030baf0 Input: the GameCube adapter and Retrode handlers keep their pads as views
Each port's buttons and sticks are decoded into its view as the report
comes in, and the getters answer from it, so both adapters' pads are read
through the snapshot bridge like the single-pad handlers.
2026-10-22 01:19:43 +02:00
LibretroAdmin 1ba48e4f8d Input: the Wii U Pro handler keeps its buttons as a view
The Wii U Pro Controller's handler now decodes its buttons into an
input_pad_view_t, which its getters read and hidraw and winusb lend,
as the other handlers do.

It also read the report wrong in two ways: it inverted three of the
report's bytes in place, in the driver's buffer, and it read up to
byte 0x0E of a report of any length. It reads the bytes inverted into
its own now, and a report too short to hold the buttons is not read.

connect_parity holds it to the old handler's buttons on every report,
and the report to being left as it came.
2026-10-22 00:37:04 +02:00
LibretroAdmin 46c424a313 android: the press grouping reads the clock only when a pad was pressed
How new a press is, is all the clock was wanted for, and it was read
at every poll while the input block timeout was on. It is read now
only on a poll a pad's button was pressed in; presses held over are
shown, and a tap let go, without it, and a pad with nothing held is
not looked at.
2026-10-22 00:21:53 +02:00
LibretroAdmin 3474e3d80d Input: the hid joypad driver is read through the snapshot bridge
With hidraw or WinUSB behind it, the hid joypad driver's pads are now
read through the snapshot bridge and lent to it where their handler
keeps a view, as the other desktop drivers' are. With a HID driver
that does not lend - the Mac's, whose button() has pads get_buttons()
does not - it is read directly, as before.

hidraw's and WinUSB's button() answer every button get_buttons() has,
not the first 32 only: a home button's menu toggle is past them, and
the bridge reads it from the buttons. hidraw_hid and winusb_hid hold
both to what the bridge assumes of a driver, over random binds and
thresholds, with a DualSense's PS button and a descriptor pad's hat.
2026-10-22 00:21:50 +02:00
LibretroAdmin 9e57c1a805 Input: the small HID pad handlers keep their state as a view too
The NES and SNES USB pads, the PS1 and PS2 adapters, the DragonRise
encoder, the HORI mini and the Kade now write their buttons and
scaled axes into an input_pad_view_t at each report, which their
getters read and hidraw and winusb lend to the snapshot bridge, as
the DualShock, DualSense and Switch Pro handlers already do. A pad
reads centred before its first report rather than at full left and
up.

connect_parity holds each to its copying handler on every report, and
its view to its getters.
2026-10-22 00:21:46 +02:00
LibretroAdmin f64e13f040 Input: the 0079:0011 pads' handler is named as the pads name themselves
Microntek's chip gives one id and one name, "USB Gamepad", to pads of
every make - NES and SNES pads, Saturn pads - and reports them all the
same way: four face buttons, four shoulder buttons, Select and Start.
Nothing a pad sends tells which make it is. Its handler called every
one of them "Generic NES USB Controller", which a Retro-bit Saturn pad
is not; it is called "USB Gamepad" now, as the pads call themselves.
2026-10-21 23:33:35 +02:00
LibretroAdmin 628ea4dcf7 android: the input block timeout keeps presses back instead of waiting
The poll waited up to the input block timeout for an event, and again
after each one, so that a press a pad sends as several events a few
milliseconds apart landed in one poll. That stalled every frame by the
timeout, pressed or not, on the main thread.

The poll no longer waits in the foreground. Instead a pad's presses in
a poll are shown at its end unless the newest of them came within the
timeout of it, by Android's own event times: then they wait for the
next poll, and the rest of the press, coming before that, is shown with
them. Nothing waits more than one poll, an older press is shown at
once, and a press let go before it was shown is shown for one poll. At
0, the default, nothing is kept back and it costs nothing.

The keeping back is android_press_group.h, which
samples/input/android_block_timeout holds to all of that.
2026-10-21 23:33:32 +02:00
LibretroAdmin 9e1c1e8ac8 Input: a pad on 0079:0011 is the NES handler's only by its name
DragonRise's and Microntek's chips give one id, 0079:0011, to pads of
every layout - NES and SNES pads, Saturn pads, arcade sticks - and the
pad table took every one of them for the NES pad. A Retro-bit Saturn
pad on Windows came up as "Generic NES USB Controller", read through
the NES handler's layout.

The NES handler now takes such a pad only when it calls itself "USB
Gamepad", as that NES pad does; any other is read from its report
descriptor and known by its own name. A driver that gives no name
gets the NES handler as before. winusb now reads every pad's own name
and looks the handler up by it, not by the ids alone, and logs it.
2026-10-21 22:49:21 +02:00
LibretroAdmin b4b8ceb2db udev: a rumble effect's refresh is due from when its pass began
The writer read the clock again after writing the effect, to set when
it is refreshed. A clock that moved in between - the test's, which it
moves on as soon as it sees the effect - put the refresh a whole
period later, and udev_rumble_test failed one run in two or three. The
pass's own clock, read before anything is written, is used instead,
which is also one clock read fewer per effect played.

The test's waits for an event that is due are 3 s, not 500 ms: one
that comes returns at once, and a sanitizer build on a busy runner can
be slower than that.
2026-10-21 22:12:10 +02:00
LibretroAdmin 0e2cc5da04 android: the input block timeout is waited after each event again
Input Block Timeout lets a pad that sends one press as several events
have them all taken in the same poll: the poll waits up to the timeout
for an event, and again after each one, until none follows within it.
The change that stopped the poll draining the battery in the
background waited only for the first event and none after, so the
setting did nothing any more.

In the foreground the timeout is waited after each event again;
backgrounded the poll still waits for whatever wakes it and then
drains the queue without waiting. The waits are in android_poll_wait.h,
and samples/input/android_block_timeout holds them to both, with the
first-event-only way asserted to be caught.
2026-10-21 22:12:06 +02:00
LibretroAdmin 86ec2fd07f CI: the Win32 display test under Wine gets its own Wine prefix
Run in the shared prefix after another Wine program had started it
before the dummy display was up, Wine listed only that display's one
mode, and the test failed on "enum listed 1 modes, desktop flagged 0".
A prefix of its own probes the dummy display's RandR modes fresh.
2026-10-21 22:12:02 +02:00
LibretroAdmin 01f1aa65d5 Input: rumble pads on WiseGroup's PlayStation adapters on Windows
The MP-8866 Dual USB Joypad, SmartJoy PLUS and Super Joy Box 3 rumble
the pads in them through an output report Windows' own HID driver
never sends. As Linux's hid-sjoy has it, its first three values are
on, the small motor on or off and the large motor's force; Windows'
parser places them from the device's own descriptor, so no driver and
no byte layout is needed.

winraw writes it from its rumble thread, and winusb's generic pads at
the poll, through the shared input/common/hid_sjoy_output.h. The log
says whether the adapter's report was found.
2026-10-21 22:11:56 +02:00
LibretroAdmin bc41f9e826 samples: connection_gate's stress readers yield between reads
Four readers spinning on the gate without a break, on a two-core
runner under a sanitizer, keep it from ever being seen empty, and the
retirement waiting for that starved until the 60 s timeout. A reader
reads a few times a frame; the stress readers now yield as one would.
2026-10-21 22:11:53 +02:00
LibretroAdmin 86fc5aa7fb Input: HID pads lend their state to the snapshot bridge
The DualShock 3, DualShock 4, DualSense and both Switch Pro handlers,
and the generic pads read from their report descriptors, now keep
their state as an input_pad_view_t, decoded straight into it and read
by their getters. hidraw and winusb hand reports to the handlers only
in their poll, so the state does not change between polls and they
lend it through a new hid_driver_t view(), as udev and winraw lend
theirs: the bridge reads it in place, with nothing copied.

The beside layer passes view() through, so a WinUSB pad beside
winraw is lent, and winraw's own pads stay lent while it is there
instead of falling back to copies. A generic pad's hat is part of its
view, which the drivers' hat reads now use.

connect_parity holds each converted handler's view to its getters
after every report, and the DualShock 3's axes to the copying
handler's; hidraw_hid, winusb_hid and the frontend harness's beside
lane check the lend.
2026-10-21 22:11:49 +02:00
Codex 9bcf16a4af h265: recognize Annex-B parameter extradata correctly
Use the shared leading start-code detector for three- and four-byte Annex-B extradata. The reversed boolean check rejected documented Annex-B input and mistook an hvcC compatibility byte for a start code.

Six regression checks fail on the old implementation and pass after the fix. The full HEVC lossless oracle passes with ASan/UBSan in threaded, non-threaded and volatile-atomic configurations; malformed-input tests also pass.
2026-10-21 22:04:19 +02:00
Codex c25df2962c test: retire Metal frontend recordings on viewport changes
Wrap the existing changing-color/audio core with an opt-in geometry change at frame 90. Require direct and threaded native frontend recording to finalize before later frames and independently decode the resulting file after playback continues.
2026-10-21 21:35:23 +02:00
Codex e69e9db6fb test: exercise native frontend recording history and startup retirement
Build with the builtin player and no FFmpeg, verify each successful native recording is published to video history, and exercise quit during startup plus existing-file startup failure. Keep output preservation and bounded shutdown mandatory.
2026-10-21 21:31:50 +02:00
Codex 02a9b96c5e media: retain video history in builtin-only player builds
Enable video-history storage, lifetime, playlist display/actions and native recording publication when HAVE_WEBMPLAYER supplies playback without FFmpeg or MPV. Classify builtin movies consistently during history/pre-load handling.

Validated a full FFmpeg/MPV-disabled builtin-player release build, actual MP4 playback and the resulting movieplayer/builtin video-history entry. The Metal controller history branch also compiles with warnings as errors and its 2 KiB frame budget.
2026-10-21 21:31:17 +02:00
Codex bd24518091 test: independently decode Metal frontend AAC tones
Check actual decoded sample count within AAC priming/padding tolerance and verify distinct 440/660 Hz channel amplitudes. Metadata and duration alone cannot establish audible frontend recording output.
2026-10-21 21:27:19 +02:00
Codex 56e9309030 test: validate native Metal frontend recordings independently
Run eight real frontend H.264/HEVC recordings with threaded/direct video and optional AAC. Independently probe packet clocks and counts, decode changing center colors, and verify audio negotiation and duration after asynchronous shutdown. Preserve native logs and movies on failure.
2026-10-21 21:26:17 +02:00
Codex 5fa3101712 test: expose the Metal frontend sample all target
Keep the sample compatible with the repository sample sweep, which invokes make clean all before make check.
2026-10-21 21:23:00 +02:00
Codex d6790368da record: capture Metal using the negotiated output encoding
Pass Context.hdrOutputMode to the recorder so failed HDR colorspace negotiation is treated as the actual SDR output. The private requested mode may remain HDR while the negotiated mode is off.
2026-10-21 21:22:00 +02:00
Codex 4d84fdbd39 test: cover Metal frontend PCM clock and backpressure
Exercise production audio admission alongside capture timestamp deduplication. Verify unarmed startup, source-sample timestamps, failed whole-packet publication and rate-change cancellation. Remove unnecessary GNU linker section flags from the extracted-function harness.
2026-10-21 21:20:54 +02:00
Codex 0e52e234ac record: integrate native Metal recording with frontend lifecycle
Capture the final Metal drawable on its existing command buffer and service owned VideoToolbox/AVFoundation recording on the dedicated worker. Share frontend GPU lifecycle hooks with NVENC, retain in-flight snapshots through stop, and preserve source timestamps across repeated presentations and allowed drops.

Add sanitizer coverage for capture backpressure and output changes, plus a full native frontend build gate. Native frontend runtime acceptance remains pending.
2026-10-21 21:19:23 +02:00
Codex 3a67b09a3f record: select advertised hardware encoder IDs for shared media engines 2026-10-21 21:00:24 +02:00
Codex dee168f65c record: prepare dedicated Metal session dispatcher and device inventory 2026-10-21 20:52:16 +02:00
Codex 6ae987f33c test: model 64-bit encoder GPU selection on every host 2026-10-21 20:51:09 +02:00
Codex a6efc1d4b8 record: prepare Metal-device cold recording session startup 2026-10-21 20:41:59 +02:00
Codex 97001b049c test: stress owned recording session producer closure with TSan 2026-10-21 20:36:41 +02:00
Codex 92c52b0f74 test: gate native recording session failure retirement 2026-10-21 20:35:18 +02:00
Codex e22a605ea8 record: own Apple recording session binding and retirement 2026-10-21 20:33:01 +02:00
Codex c50ba6190f record: regenerate clean aperture with cropped HEVC format 2026-10-21 20:24:49 +02:00
Codex 8c2c1f858f test: verify MP4 sync flags and indexed keyframe seeks 2026-10-21 20:23:53 +02:00
Codex db1cded84a test: drive native MP4 and PCM through the A/V worker pump 2026-10-21 20:21:47 +02:00
Codex 361d4c2f01 record: coordinate bounded A/V mux backpressure and dual drain 2026-10-21 20:21:40 +02:00
Codex 5b6271fc81 record: queue timestamped PCM with bounded borrowed packets 2026-10-21 20:21:33 +02:00
Codex db2a147c43 record: scope SDK inline compatibility and test mux startup contracts 2026-10-21 20:19:38 +02:00
Codex 2f1df05f44 test: exercise native compressed MP4 and AAC mux candidate 2026-10-21 20:19:38 +02:00
Codex d7b789de2b record: prepare compressed AVFoundation mux native candidate 2026-10-21 20:19:38 +02:00
Codex 1de95ca044 record: preserve cropped Main10 pixels with aligned GPU surfaces
VideoToolbox produced large right-edge errors when encoding a cropped Main10 surface directly. Capture the requested active image into an aligned physical surface, initialize its borders on the GPU, and publish the requested HEVC conformance window through rebuilt CoreMedia format descriptions.

The worker shares compressed data buffers and preserves sample attachments while keeping normalized packets borrowed until acknowledgement. An optional owned context retires cached formats only after normal drain or encoder teardown and GPU retirement. Existing operation tables and the generic physical-size encoder API remain compatible.

Keep the original requested geometry and pixel limits. Validate static/changing SDR, PQ and scRGB under ARC/MRC, independent decoded pixels/HDR tags, and C89 2 KiB frame, sanitizer and ownership checks. No CPU picture copy/readback, render-thread wait or software encoder fallback is added.
2026-10-21 20:17:10 +02:00
LibretroAdmin 8abd740051 GL: refuse a context with no OpenGL behind it
A GL build run headless with a core that asks for a hardware context
picks glcore, and the only context there is the null one, which has
no get_proc_address. gl3_init went on to load its entry points through
a null pointer and crashed; gl2 would do the same.

Both drivers now fail init cleanly when the context cannot give them
OpenGL, gl3's cleanup no longer touches a context it never got, and
the widgets are only set up when a video driver actually came up.
This is what crashed the frontend-overhead harness on GL builds.
2026-10-21 19:41:13 +02:00
Codex 793afc25f4 test: isolate VT Main10 edge padding and parameter headers 2026-10-21 19:12:39 +02:00
Codex 75e411a64d test: isolate Main10 capture pixels and encoder buffer reuse
The preserved native artifact has identical right-edge errors under three independent FFmpeg versions. Check every captured input against the double-precision reference before submit and add fresh CPU-staged encoder-pool controls. These fixture-only reads/copies do not change production capture, ownership or pixel thresholds.
2026-10-21 19:03:49 +02:00
Codex d850888998 record: bound AAC rate selection to returned property data
AudioFormatGetProperty may return fewer ranges than its advertised capacity. Iterate only the returned complete entries, reject oversized or malformed responses and ignore invalid ranges before falling back. Share the helper with the pending native recording mux.

Validation: GCC and Clang C89, 2 KiB frame limit, ASan/UBSan; shortened response reproduces the old invalid rate selection. Native framework validation remains pending.
2026-10-21 19:00:22 +02:00
Codex c0ff9e7cc3 test: preserve native VT pipeline pixel failure evidence 2026-10-21 18:56:51 +02:00
Codex f6dd470948 record: connect Metal capture ownership to VT queue worker
Add a worker pump over the existing fixed SPSC queue. GPU readiness precedes encoder submission; BUSY retains input, compressed samples stay borrowed through output acknowledgement, partial releases are retryable, and cancellation retires GPU work without encoder reuse. Native bindings connect the Metal capture and VideoToolbox adapters. Require a one-frame encoder delay bound with at least two slots to avoid fixed-pool exhaustion.

Add real-queue failure and 5000-frame threaded tests, plus an independent ARC/MRC native capture-to-encode oracle. GCC/Clang C89, frame budgets, ASan/UBSan, TSan and MinGW/Wine pass locally. Native Apple compilation/execution remains pending CI; mux/audio and renderer/controller integration are not implemented by this commit.
2026-10-21 18:41:25 +02:00
Codex 4819f76d45 record: add fixed Metal capture pool and native pixel oracle
Retain a fixed CoreVideo NV12/P010 pool and writable Metal plane views at setup. GPU conversion crops RGB8, PQ RGB10A2 or linear scRGB into limited-range encoder planes; completion publishes slot ownership without a CPU pixel copy, readback or wait. Producer/worker quiescence and explicit release gate destruction.

Add independent ARC/MRC native capture compilation and double-reference pixel/lifetime tests, separate from hardware encoder availability. This component is not wired to a renderer or recording driver. Local Python/workflow/embedding checks pass; native Objective-C/MSL compilation and GPU execution remain pending hosted Apple CI.
2026-10-21 18:32:18 +02:00
Codex 62d817999e record: add bounded VideoToolbox hardware encode adapter
Add a worker-only compression session with hardware-required startup and verification, explicit H264/HEVC8/10 SDR/PQ configuration, immutable retained inputs, bounded callback tickets, FIFO borrowed packets and ordered drain. No recording driver or renderer selects it yet; Metal capture and AVFoundation mux/audio integration remain unfinished.

Add fault-injected C89 ownership/session tests, ASan/UBSan, TSan and MinGW coverage. A separate native Apple oracle requires hardware and independently checks coded tags, YUV pixels and source timing; unavailable cases remain incomplete acceptance. Native compilation and hardware execution are pending the new hosted workflow.
2026-10-21 18:24:07 +02:00
Codex 7b3c6f159b record: admit active HDR output with truthful MP4 color
Select P010 BT.2020/PQ for HEVC and AV1 when the renderer reports active PQ or scRGB. Preserve SDR BT.709 admission, refuse H.264 HDR, and stop before a changed output encoding enters an existing session. Reuse GPU conversion without frame-path waits or readback; leave unknown mastering and content-light metadata unspecified.

Derive MP4 nclx from encoder configuration and refuse contradictory AV1 sequence tags. Add independent HDR bitstream, native frontend pixel/audio and output-mode guard coverage.
2026-10-21 18:10:44 +02:00
LibretroAdmin 48f9503c16 Input: udev and winraw lend the snapshot bridge their pads
The snapshot bridge copied each pad out of its joypad driver at the
first read after a poll: the buttons, then each axis and hat a reader
had asked for, a call into the driver for each. A driver whose state
changes only in its own poll needs no copy: what it holds is the same
until the next one.

Such a driver now lends it. The pad's state - buttons, the first
sixteen axes as the reads answer them, four hats' directions - is kept
by the driver in one layout, input_pad_view_t, as its events and
reports arrive, and a new view() hands the bridge a pointer to it once
a poll; the readers read it in place. udev keeps it with each event;
winraw keeps its pads in it outright, and lends them where its reports
are read in the poll. Other drivers are copied as before, and the copy
now walks only the axes and hats in it, not all twenty: under
callgrind that halved what taking it costs.

frontend_overhead's core-view lane runs its scenarios a third time
with the pad lent, and must give the same digests while asking the
driver for nothing but the view; joypad_bridge_checked holds udev's
lent state to its reads, winraw_joypad_newest winraw's.
2026-10-21 18:06:16 +02:00
Codex 5b6ff668a6 data_transfer: keep inert holes inaccessible during refill
Non-strict discard left permanent inert intervals writable. On Linux with transparent huge pages, refilling adjacent live bytes could allocate backing for those holes again and fail the prefix residency contract. Protect permanent holes on every build, while preserving the existing ordinary discard policy.

Request huge-page backing in the refill regression: the previous implementation fails its mincore assertion, while the fix passes. All four data-transfer samples pass with ASan/UBSan; the C89 strict prefix test also passes its access-fault checks.
2026-10-21 17:54:34 +02:00
Codex 7663c228a9 rgui: prefer GPU media playback on supported renderers
Use the shared surface player first when Vulkan or D3D advertises NV12 imports, retaining CPU admission when needed. Require imported hardware frames in native acceptance and compare nonzero first-picture timestamps without retiming playback. Record the independently isolated NVIDIA H264 host-header lifetime limitation rather than suppressing validation.
2026-10-21 12:22:48 +02:00
Codex f276def840 test: add H264 host-header lifetime conformance probe
Add optional external MP4 sample comparisons and a standalone probe that mutates the outer picture header after recording returns. This reproduces delayed host-header access in the native driver without changing production decoding or disabling validation.
2026-10-21 12:16:07 +02:00
Codex 810e9e2aac webm: support bounded VP9 hardware seeks
Keep VP9 preroll on the admitted hardware decoder. Discard one packet per step, retain submission state across backpressure, and preserve the selected target packet for normal output. Cover partial input, superframes, replacement seeks and leases surviving decoder reset.
2026-10-21 12:06:30 +02:00
Codex 1c12cc8dc7 test: save the compared native media capture
Avoid requesting a second viewport readback immediately after a successful seek comparison. D3D11 can interpret the second request as a recording-ring read before an older slot is ready. Save the capture whose pixels were actually compared.
2026-10-21 12:02:22 +02:00
Codex 6c60da0189 test: include Windows video imports in the Vulkan oracle
The extracted import functions now reference the Win32 import cache and packed pipelines. Extract their production types and helpers, enable Windows Vulkan declarations before the first header, and track the new source dependency so MinGW builds remain complete.
2026-10-21 11:17:08 +02:00
Codex 0229f6f508 rgui: present hardware movies through streaming surfaces
Retain software thumbnails where available and use the shared GPU player when CPU admission fails. Publish draw parameters through an atomic mailbox, cancel pending image tasks on retirement, and keep transport ownership on the main thread.

Exercise fullscreen and inline rendering, repeated seek retirement, and failed-file recovery through the real menu. Split wallpaper path resolution to keep the Windows RGUI stack frames under four KiB.
2026-10-21 11:14:36 +02:00
Codex f7ddfade87 vulkan: decode AV1 through D3D11 on NVIDIA 610.47
NVIDIA 610.47 faults on AV1 intra-block-copy pictures submitted through Vulkan Video. Select D3D11 AV1 decoding on the same adapter when shared texture and timeline-fence imports are supported. Other codecs keep their Vulkan backends.

Pack exact 8/10-bit YUV samples into shared FP16 textures on the decode GPU, then apply the existing Vulkan colour conversion once. Retain output leases and fences, cache imports by generation, and reserve all descriptor types required by the shared set layout.

Native AV12 seek stress, rendered PQ/HLG reference checks, C89 builds, portable codec CI and import-allocation fault injection validate the path.
2026-10-21 10:39:59 +02:00
Codex 8fdb625658 test: link shared media helpers in thumbnail animation harness
The real thumbnail implementation now calls the shared playback and CPU retirement helpers. Build both with the harness frontend flags and include their objects in linking and cleanup.
2026-10-21 10:04:31 +02:00
Codex 181ea3aca2 media: recover transport after a failed seek target
Keep the last picture and accept another seek after a seek callback fails. Pause the frontend session and discard partial seek audio instead of retiring the player. Relative controls advance from the failed target so repeated forward/back commands can escape an unsupported section. Cover recovery and GPU lease ownership in inline and threaded player tests, plus relative control targets.
2026-10-21 09:21:45 +02:00
LibretroAdmin 547fa2369c Input: winraw reads a DualShock 3 on Sony's sixaxis.sys
A DualShock 3 on Sony's own driver, sixaxis.sys, or on DsHidMini in
the mode that stands in for it, sends no input reports: its state is
feature report 0, asked for. Under the raw input joypad driver such a
pad showed up and did nothing.

The driver's own thread now asks a DualShock 3 for that report every
4 ms and hands the newest to the poll through three buffers swapped
with an atomic exchange; the poll lays it out as the pad's own HID
description does - buttons 1 to 19, the sticks X, Y, Z and Rz - so a
profile for the pad binds it. Its motors and player light go as that
driver's commands. A DualShock 3 on Windows' own HID driver, which has
not started it, has no report 0: it is asked once and left alone, and
one whose input reports come after all is no longer asked. With no
such pad nothing changes: the thread waits on wakes alone, and the
poll looks at one counter.

The writer thread's timed wait now goes through a high resolution
waitable timer on Windows, where a short wait was otherwise a whole
timer tick.

winraw_joypad_sixaxis_test runs the driver under Wine with the pad's
driver faked.
2026-10-21 09:10:49 +02:00
LibretroAdmin 2fa21b3b35 Input: the hid DualShock 3 rumbles
Its handler took rumble and dropped it. The motors now go in the
output report with the player lights - the strong motor's force, the
weak one on or off, which is all it takes - sent from the packet
handler at the pad's next report, once for each change, so nothing on
the frontend's thread waits for the pad.

hidraw_hid_test checks the report, the motors stopping, and that an
unchanged value is not sent again.
2026-10-21 09:10:41 +02:00
Codex 3dffd71f65 test: stop RGUI media checks after decoder admission fails
A missing CPU player leaves transport callbacks unset. Report the initial decoder failure and clean up instead of continuing into a null seek callback.
2026-10-21 09:05:35 +02:00
Codex c0822859a5 test: check HLG AV1 imports through fullscreen menus
Allow native fullscreen acceptance to require HLG transfer metadata while retaining PQ as the default. D3D12 MP4/WebM seeks pass across texture menus and direct/threaded presentation, with independent SDR color reference checks.
2026-10-21 09:00:27 +02:00
Codex 3f90f6dc56 d3d12: distinguish screenshot buffers from streamed recording
Latch active recording separately from readback ring allocation. Repeated screenshots must capture the current frame rather than select an unfilled recording slot. Exercise consecutive screenshots and nonblocking recording selection with production code on Linux sanitizers and mingw/Wine.
2026-10-21 08:53:06 +02:00
Codex 3c44f45008 media: batch resident seek progress within the worker budget
Advance metadata and decoder seek work for up to 4 ms or 1024 steps per callback. Keep the existing feeder I/O budget and yield for decoder waits or a span outside the resident window, avoiding one main-loop round trip per metadata packet. Cover completion, waiting, failure, step limits and feeder boundaries.
2026-10-21 08:45:57 +02:00
Codex 05c634348c ci: skip synthetic phase grid on hosted Metal runners
Hosted macOS wait measurements exceed the synthetic 120 Hz grid tolerance. Explicitly skip that timing lane while retaining all other Metal harness checks and the default phase gate on other hosts.
2026-10-21 08:43:09 +02:00
Codex 2b93eb9bba test: verify changing HDR metadata through fullscreen menu seeks
Check P010 source color metadata after rendered forward, backward and burst seeks, alongside existing image round-trip checks. Keep the optional fixture contract explicit and all inspection within the test harness.
2026-10-21 08:27:51 +02:00
Codex 8692c5a355 media: learn AV1 HDR seek checkpoints during WebM playback
Advance the contiguous metadata cache only after complete video packets, preserving the duration cursor's lookahead packet. Seed checkpoints from configuration and avoid extra input reads. Exercise playback-derived cue checkpoints and force duration lookahead beyond a four-entry opening table in the regular stream test.
2026-10-21 08:20:57 +02:00
Codex 8b86fe7b30 media: learn AV1 HDR seek checkpoints during MP4 playback
Record metadata only after a packet is fully consumed, advancing the contiguous cached prefix without extra reads. Track packet ordinals independently of displayed pictures and seed the initial checkpoint from coded configuration. Repeated seeks into a played prefix can reuse metadata instead of scanning it again. Cover playback-populated checkpoints with changed HDR metadata and no preroll submissions.
2026-10-21 08:13:41 +02:00
Codex 405fda8bd5 media: restore AV1 HDR metadata before WebM cue seeks
Prepare uncached metadata through a bounded independent header cursor, exposing its input and retained packet spans to the feeder. Cache metadata changes for repeated seeks and preserve validated initial sequence state when earlier in-band bytes are unavailable. Check retained payload availability and restart malformed records after reset. Cover changing timelines, backward and replacement seeks, partial input and malformed metadata recovery.
2026-10-21 08:05:49 +02:00
Codex f6f76ff4a2 media: restore AV1 HDR metadata preceding MP4 seek points
Scan uncached sample headers with bounded work and feeder yields before selecting a random access point. Cache metadata changes for subsequent seeks and restore the state entering the selected packet. Restart malformed header scans from the packet boundary after reset. Cover changed CLL timelines, partial input, replacement and backward seeks, cached work, and malformed reset recovery.
2026-10-21 07:54:24 +02:00
Codex b83c74b282 test: verify fullscreen media through native menu rendering
Add an optional real-renderer mode to the four menu transport harnesses. Check paused seek image changes, exact backward restoration and latest burst delivery through the actual presentation path; preserve the existing null-driver checks. Keep capture and polling confined to the harness.
2026-10-21 07:42:30 +02:00
LibretroAdmin 8cbe988eaf Input: hid pads light the port they are read on
Every hid pad handler that lights the pad - the DualShock 3, DualSense
and Edge, Switch Pro, Switch 2 Pro and Wii Remote - lit its hid slot,
once, when it started. After a driver restart the pads can come back in
other slots and are moved back to the ports they had, and a port can be
pointed at another pad in the menu or by a reservation: the lights then
showed the wrong player, and a pad no port reads was still lit.

The frontend now tells the joypad driver which port reads each pad,
and the hid drivers pass it to the pad's handler, which relights from
its packet handler - the pad's own thread - so nothing waits. The lowest
port reading a pad is shown; on none, the lights go out. The Switch
pads take the console's patterns past player 4.

It costs one countdown a poll: every 32 polls the ports are compared
with what the driver was last told, and only a change, a driver start
or a pad starting beside the joypad driver tells it again. The player
is kept for each slot, so a pad plugged in is lit for its port as it
starts. A handler never told, under a hid driver that does not pass it
on, lights its slot as before.

joypad_slots covers the slot's player across pads and threads;
connect_parity the DualSense and Switch Pro relighting.
2026-10-21 07:36:04 +02:00
Codex b5e181772e media: prepare initial AV1 HDR metadata before MP4 seeking
An early seek can skip the only sample carrying in-band CLL/MDCV. Prepare its initial metadata without GPU submission, yielding after cursor changes and at most 32 OBU steps or 4 ms. Recheck sample residency on resume, retain the borrowed byte frontier, and cancel preparation when a newer seek arrives. Seed decoder resets after configuration metadata.

Regression fixtures expose metadata loss and premature sample reads before the feeder handoff. Final checks pass Linux formats with sanitizers, the Windows AV1 suite, strict C89/2 KiB compilation, 96 native MP4 seeks and 16 native WebM regression seeks.
2026-10-21 07:06:02 +02:00
Codex 4a2c14bb85 media: seek AV1 WebM through bounded hardware preroll
Select the preceding cue and keyframe, retain the target packet, and preroll without exporting discarded pictures. Yield on missing input or decoder backpressure, with a 32-step / 4 ms budget. Cache initial static HDR metadata during the existing opening scan so an early seek can skip its source picture safely.

Validate cancellation, decoder errors, held leases, partial endpoint discovery and timing lookahead. Linux formats with sanitizers and the Windows AV1 suite pass; 24 native D3D11/Vulkan cases verify 96 seeks against independent pixels and metadata.
2026-10-21 06:54:15 +02:00
Codex b284637056 media: carry AV1 static HDR metadata in shared frames
Parse bounded CLL and MDCV metadata OBUs, including configuration OBUs, and normalize AV1 fixed-point units to the existing frame metadata ABI. Snapshot metadata with exported leases and retain first-picture static metadata as the fallback after decoder resets.

Reject conflicting explicit container metadata with allowance for source precision. Preserve state on malformed input and mark an unrepresentable mastering maximum absent instead of wrapping. No core HDR ABI, production waits, readbacks or locks change.

Add C89 unit and stream-lifetime checks, reproducible independently verified HDR fixtures, and native D3D11/Vulkan metadata and MP4 seek checks. Track the actual held picture in the retirement oracle. WebM AV1 seeking and broader player/physical acceptance remain separate open work.
2026-10-21 06:34:46 +02:00
Codex 83f586b91a input: use memmove for Switch 2 rumble report copy
Keep the two rumble payloads identical while satisfying the same-buffer copy check. The six-byte ranges are disjoint; memmove preserves the report without an allowlist exception.
2026-10-21 06:34:09 +02:00
LibretroAdmin 39de0dbebf Input: light a Switch 2 Pro Controller's player once it reports
Sent at the end of the wake, the player's lights stayed off. They now
go from the reader after the pad's first report, as SDL sets them once
the pad is open, and a refusal is logged.
2026-10-21 06:09:40 +02:00
Codex 6d8ae39d66 vulkan: admit AV1 PQ and HLG hardware decode
Decode supported HDR matrix/gamut combinations without changing coded samples, using the existing container colour metadata and GPU import path. Keep unsupported matrices, gamuts, film grain and geometry refused.

Check generated HDR transfer/range tags on container and builtin-core frames, and require contiguous core output from frame zero. Retain independent decoded-pixel, ownership and refusal checks. Clear oracle-owned frames after a colour mismatch so failure cleanup cannot abandon a lease twice. Physical HDR presentation remains a separate acceptance gate.
2026-10-21 06:03:27 +02:00
Codex 5e74e2d29d media: admit AV1 PQ and HLG shared-frame decoding
Allow supported HDR matrix/gamut combinations in D3D11 AV1 admission and the WebM AV1 hardware reader. Preserve coded samples and carry source colour through the existing shared-frame importer; keep unsupported matrices, gamuts, film grain and geometry refused.

Add independent 10-bit PQ/HLG full/limited-range fixtures, container/core colour assertions and portable WebM admission tests. Pack P010 directly from libdav1d planar samples to avoid the reference scaler remapping full range to limited range. Native pixels remain exact in copied and direct-DPB modes. Physical HDR presentation and the remaining AV1 tools require separate acceptance.
2026-10-21 05:55:12 +02:00
LibretroAdmin 14689b7221 Input: a hid handler for the Switch 2 Pro Controller
The Switch 2 Pro Controller sends nothing on its HID interface until it
is woken through the bulk pipes of its second interface. The pad has
Windows bind that one to WinUSB itself, so on Windows it now works with
the hid driver out of the box: the pad's HID collection is opened once
its WinUSB interface is there - the one on the same pad, found by their
common parent - and its reader sends the wake, then lights the
player's lights, before it reads. Nothing on the frontend's thread
waits on the pad; an answer that does not come is given up on. Where
another program holds that interface, it woke the pad, which is read
as it is.

The handler reads the buttons, grips and sticks of its 64-byte report
and drives rumble on both hands from the poll, kept up while it lasts.

connect_parity holds the handler to the report and rumble;
winusb_hid_test the wake.
2026-10-21 05:48:41 +02:00
Codex a94bf25c9c video: compare present timestamps after querying the driver
A present timestamp can advance while the driver callback runs. Read the comparison clock after the callback so a newly reported display tick is not rejected as being in the future. This moves the existing clock read without adding a frame-path wait.

Add a threaded harness callback delayed across a clock tick and inspect the phase flag on its owning video thread. The unchanged scheduler fails this regression; the correction retains display phase. Native Metal pacing acceptance remains outstanding.
2026-10-21 05:46:40 +02:00
Codex 18d2e1df1d video: add D3D11 AV1 Main hardware decoding
Map rav1 transactions into DXVA AV1 picture and tile buffers. Offer fixed-size SDR Profile 0 8/10-bit 4:2:0 decoding when shared NV12/P010 imports are available, using the existing exclusive decoder devices, slot renaming, fences and retained leases. Refuse unsupported sequences, film grain, resizing and HDR.

Keep the unused wire feedback number zero to avoid the NVIDIA reordered-stream fault. Add SDK layout, tile-bound, refusal, packet ownership, native pixel and builtin-core oracles, plus independent fixture generation.
2026-10-21 05:32:58 +02:00
Codex e4d20c791d media: hold the AV1 startup clock on decoder backpressure
Keep the MP4 hardware startup clock steady while its first picture waits for input or decoder capacity. Advancing it on each retry made the core discard the first picture to catch up. Preserve normal pacing once a display slot has advanced, and assert the first presented slot after decoder, output and sink backpressure.
2026-10-21 05:25:52 +02:00
Codex 052282cf71 vulkan: refuse AV1 intra block copy on NVIDIA 610.47
The Windows 610.47 driver faults inside vkCmdDecodeVideoKHR on a valid intra-block-copy keyframe. The same file also crashes independent FFmpeg Vulkan decoding. Refuse this picture through the existing unsupported path before recording GPU work, preserving its codec flags. Scope the guard to the confirmed vendor and driver version.

Populate physical-device properties in decode-only probes and check that refusal does not advance command or timeline state. This contains the application crash; playback through the affected section still requires fallback or a corrected driver.
2026-10-21 05:11:10 +02:00
Codex 8f57c1561f media: batch ready AV1 seek preroll work
Bound AV1 MP4 seek work to 32 progress steps or 4 ms per poll, yielding immediately for missing input or decoder capacity. This avoids a frontend round trip for every hidden or discarded preroll frame. Cover input and decoder backpressure and completing ready preroll in the hardware reader test.
2026-10-21 05:01:48 +02:00
LibretroAdmin 4980833c22 Input: a hid handler for the Switch Pro Controller
The Switch Pro Controller is read by the hid driver natively, on USB
and Bluetooth, through the system's own HID driver - nothing to
install, on Windows as elsewhere.

On USB it is greeted as the console does before it sends anything;
over Bluetooth it is asked for its full reports. Its commands - rumble
on, the player's lights, the sticks' calibration from its flash - go
one at a time from the packet handler, each on the last one's answer
and again if one goes unanswered, so nothing waits. Until the full
reports come, Bluetooth's short report is read. Rumble goes from the
poll, kept up while it is held.

hidraw now tells a handler a pad is on Bluetooth from the node's bus,
as Windows' driver already does, so the pad is not greeted there.

connect_parity holds the handler to the pad's reports, greeting and
commands; hidraw_hid_test to the bus.
2026-10-21 04:43:14 +02:00
Codex 45a28f037e tests: publish the synthetic display grid safely
Publish the period atomically, snapshot it once per callback, and initialize the grid origin before installing the fake driver. This removes the main/video-thread race and avoids dividing by a period that changes during the callback.

Retain failure-only phase samples in the harness to distinguish reserve variation and late releases on the unresolved native Metal gate. No production pacing instrumentation or threshold changes.
2026-10-21 04:04:55 +02:00
Codex 238376bf27 tests: isolate synthetic display from Cocoa event work
The fake display does not submit native presents. Do not pump Cocoa events during its phase measurements: variable event work feeds the core-time reserve and moves release phases independently of the fake display. Keep event pumping for every real-backend lane and retain the existing phase thresholds.
2026-10-21 03:54:22 +02:00
Codex 284327c612 tests: keep synthetic display phase independent of real swaps
The driver-pacing phase lane supplies a synthetic 120 Hz vblank grid and a backend that releases immediately. Do not present through the underlying real renderer while that grid is active: its swap clock and variable render cost otherwise move the phase under test. Other lanes still call the real renderer. Preserve the 24-of-30 phase threshold, cadence bound and toggle-phase tolerance.
2026-10-21 03:34:46 +02:00
Codex d7957a50ac tests: feed and drain bounded container audio seeks
Accumulate caller-paced short reads when preparing the linear PCM oracle and comparing seek output. Re-admit the resident source before each preparation step because the metadata-to-preroll rewind withdraws its previous byte bound. Keep all 32 float and S16 seek targets, PCM comparisons, starvation, replacement and rejection checks; report a failed target explicitly.
2026-10-21 03:34:46 +02:00
Codex b705702cae tests: provide companion preview movie clock queries
The companion handoff sample supplies a fake frame-indexed animation stream. Provide the duration and frame-time queries now used by video_player, returning the same unknown movie-clock values as the real frame-indexed formats. This restores the sample link and preserves its delay-driven animation and ownership tests.
2026-10-21 03:34:46 +02:00
LibretroAdmin e1dfae341a Linux: the hid driver reads pads with no handler from their descriptor
On Linux the hid joypad driver took only pads with a handler in
input/connect; Linux gives a pad's report descriptor but, unlike
Windows and the Mac, does not parse it. input/common/hid_report_desc.h
does: it reads the gamepad, joystick and multi-axis collections of a
descriptor into a layout of fields, made once when the pad arrives,
and decodes each report into buttons, axes and a hat at the poll with
nothing allocated. Report IDs, signed fields, push and pop, usage
ranges and four-byte usages are read; output and feature reports,
arrays and constant fields are passed over.

hidraw now opens a node with no handler when its descriptor, read
from sysfs (which any user may read), holds such a pad, and reads it
through that layout, the pad known by the name it gives.

It is numbered as the Mac numbers such a pad, so its profiles bind on
all three: buttons in usage order, axes X, Y, Rx, Ry, Z, Rz then
rudder, throttle, steering, accelerator and brake, the first hat as
the D-pad. An axis named twice is given the first free axis once
every other has its own, as the Mac does; the Windows path gave it
one at once, and now does the same.

hid_report_desc_test reads a common USB pad's descriptor, which names
X four times, a pad split across two reports with a signed axis and a
hat counted from 1, and a keyboard's, which is no pad; every cut of a
descriptor and a short report are read without going past them. It
runs in CI under ASan and UBSan. hidraw_hid_test reads a pad with no
handler, and winusb_hid_test an axis named twice.
2026-10-21 03:30:09 +02:00
Codex b8b182cb88 media: schedule container audio on movie timelines
Delayed tracks previously started immediately, packet gaps collapsed, and
short audio tracks repeated before their movie's video-only tail ended.
Map internally demuxed AAC, Opus and Vorbis PCM to movie sample positions,
retaining pending PCM across silence and discarding overlap without losing
decoder history. Pad to the declared movie period before repeating.

Keep decoder source counts separate from presentation positions and MP4
edit play lengths separate from the full movie duration. Prepare seeks in
bounded metadata/header slices followed by codec preroll, including AAC
Matroska. Packet traversal yields independently of starvation and EOF.
Remove full-file WebM audio length scans at open and release AAC's owned
Matroska demuxer at teardown.

Apply Matroska CodecDelay as a timestamp offset rather than an additional
AAC pre-skip, in both the frontend and builtin core. Source-relative
caller-demuxed audio retains its external timeline owner.

Add independent PCM/timestamp references, including rational EBML timing
where FFmpeg's millisecond time base rounds CodecDelay. Cover delayed and
gapped tracks, overlap, full repeat periods, unknown duration, empty audio,
seek/reset, guarded source pages and concurrent bounded refill. Maintain
these checks in the normal sample workflow without changing HDR defaults,
the core HDR ABI or input mapping/context infrastructure.
2026-10-21 02:57:38 +02:00
github-actions e3087611d3 Fetch translations from Crowdin 2026-10-21 00:29:23 +00:00
Codex f9658be857 media: preserve WebM movie time in frontend playback
Publish the most recently shown packet timestamp from each exclusive WebM decoder owner, including VP9 and AV1 hardware paths. Use declared WebM movie periods in the shared scheduler and keep frontend controls on container movie time instead of normalizing away the leading video interval.

Keep raw bounded decoder seeks unchanged. Movie-coordinate seeks preserve requested audio positions in leading and trailing empty video intervals, while the player retains future pictures and holds the last picture through the next loop lead.

Link Opus and Vorbis into the real frontend/mixer harness and compare all 24 pictures against ffprobe-derived exact timestamp sidecars. Cover CPU and texture owners, pause/leading/tail seek, loops, VP9 retry snapshots and AV1 decode-owner timestamp/lease behavior. Known-duration inline movies retain BUSY frames; unknown-duration inline streams keep their legacy drop behavior.
2026-10-21 02:00:32 +02:00
Codex e1e3529d67 ci: make Switch overlay and HID output checks unambiguous
Use the established index parameter spelling in the three Switch overlay setters so the bounds checker recognizes their existing page-size guards. Rename only; the bounds checks and geometry behavior are unchanged.

Use memmove for the HID class pending-report copy between its fixed output buffer rows. This avoids the conservative same-base memcpy finding without an allowlist entry or changing the output queue protocol.
2026-10-21 01:53:19 +02:00
Codex fd0b29f740 settings: keep Driver Pacing help within C90 string limits
Preserve the backend, clock, conflict and pacing guidance in 460 characters. The 633-character help added by the Display Pacing integration exceeds the 509 characters ISO C90 requires compilers to support, failing the actual Griffin stand-in compile with -Werror=overlength-strings.
2026-10-21 01:43:28 +02:00
Codex c636dbab15 media: pace frontend MP4 playback on movie timelines
Carry decoder-owned movie timestamps through the existing job publication, retain inline future frames and hardware leases, and preserve the full movie period through leading edits, video tails, loops, pause and bounded seek. Start mixer audio when the presentation sink is ready, and expose the current loop position to shared controls.

Keep raw MP4 decoder seeks unchanged and translate the generic frontend seek at its movie boundary. A normal seek completes after its replacement picture is accepted; a leading-gap seek can commit without showing a future picture. Apply worker catch-up requests on the exclusive decoder owner, avoiding a rewind/reference race.

Add real CPU and texture-owner/mixer conformance with independently timestamped H264/AAC fixtures, plus worker/threadless software/hardware scheduling and cancellation cases. The fixture has video at movie seconds 1..3 and audio at 0..4; repeated video starts must occur at seconds 1 and 5.
2026-10-21 01:41:12 +02:00
LibretroAdmin e344995aea Windows: the hid driver reads pads with no handler from their layout
On Windows the hid joypad driver took only pads with a handler in
input/connect. Every other gamepad or joystick on Windows' HID driver
is now taken too and read from the layout its report descriptor
gives, as the Mac's driver reads such pads - Windows parses the
descriptor (HidP_GetUsages, HidP_GetUsageValue), nothing here does.

It is numbered as the Mac numbers it, so a profile made there binds
here: its buttons in usage order, its axes X, Y, Rx, Ry, Z, Rz, then
rudder, throttle, steering, accelerator and brake, an axis named twice
taking the first free one, its first hat as the D-pad, and the pad
known by the name it gives. A signed field is sign-extended from its
width, and a value a report does not carry stays as it was. The
layout is read once when the pad arrives; each report is decoded at
the poll into a fixed state, with nothing allocated.

winusb_hid_test reads such a pad: its buttons by usage, its axes in
that order with a signed one, its hat at rest and turned, its name,
and its layout let go with it.
2026-10-21 01:22:10 +02:00
LibretroAdmin 22c536a36f hidraw: every pad with a handler, not the DualShock 3 alone
On Linux the hid joypad driver opened a hidraw node only for a
DualShock 3, so a DualSense, DualShock 4, the USB adapters and the
rest of input/connect's pads were never reached there, though their
handlers read reports exactly as hidraw gives them: as the device sent
them, the report ID first where it has one. It now opens a node for
any pad with a handler.

hidraw_hid_test has a DualSense node opened, given its port with its
ids, and its report read through its handler.
2026-10-21 01:22:07 +02:00
LibretroAdmin f896928838 video_thread: Display Pacing keeps the vblank under Driver Pacing
With Threaded Video Display Pacing and Driver Pacing both in effect, the
hold stood aside for every frame the driver released. The driver's
minimum interval keeps the content's rate but not where the frame falls
against the display's refresh, so with vsync off nothing placed the
present any more: its phase was whatever the last hitch left - at
start-up, the content load and a state load - and the tear stayed on
screen. Toggling Display Pacing let the hold place frames again for a
moment (the menu, the Driver Pacing re-negotiation) and the driver kept
that phase, which is why the tearing went away after the toggle.

Where the presenter has the display's own vblank (present timing from
the display, DWM's vblank on Windows), the hold now still places a
released frame on it; the driver's sleep after it, a period from the
last release, has nothing left to wait, so the frame still sleeps once.
Without a display vblank the hold stands aside as before.

samples/gfx/threaded_video: the driver pacing lane gives the presenter a
vblank grid and a release that never sleeps, adds a 30 ms hitch, and
requires the released frames to keep the content's rate and one phase
on the grid, at start-up and after Display Pacing is turned off and on,
on the same phase both times. It fails on the old wrapper (30 frames in
6 ms, 5 of 30 on one phase).
2026-10-21 01:20:50 +02:00
LibretroAdmin 80398df3b1 runloop: Driver Pacing alone keeps the content's rate
With Driver Pacing in effect and nothing else holding the loop - vsync
and audio sync off, no rate control, Threaded Video Display Pacing off -
the minimum interval handed to the driver was 0, and the driver's
release, which never sleeps for a rate it was not given, let the core run
unthrottled. The interval is now the content's period there too, as it
already was under Display Pacing; the timer's period still goes where the
timer would hold, and nothing where vsync, audio or a scanline already
hold the loop. Fast-forward never reaches the driver's release.

The choice moves into runloop.h as runloop_pace_driver_interval(), and
samples/runloop/pacing checks it against the whole pace decision (the
"driver alone" row fails on the old expression).
2026-10-21 01:20:47 +02:00
Codex ccbc80145a media: honor leading empty edits for builtin MP4 video
Expose the selected video track presentation delay without changing decoder-relative seek coordinates. Gate initial presentation on that delay, preserve movie-time audio while seeking inside the leading gap, and clear stale software pixels on reset or backward seek. Reject a delay/span combination that cannot fit the presentation clock.

Add independently timestamp-checked paired fixtures and real-core pixel/PCM comparisons covering playback, reset, seeks before and after the video start, and state restoration inside the leading edit.
2026-10-21 01:14:54 +02:00
Codex ef7933e682 media: preserve audio packet presentation timelines
Retain container timestamps and exact codec sample boundaries when gathering audio. Schedule silence and overlapping-source discard through one playback/seek reader, preserve laced packet continuation, and account for codec priming and container edits without double trimming.

Recognize the common leading empty MP4 edit and reject unrepresentable delays. Initialize fully resident Opus track length before decoder creation. Preserve borrowed table bindings when metadata grows and latch failed growth.

Cover delayed, gapped and overlapping tracks, independent trimmed PCM slices, reset/restore, parser bounds and allocation failure with maintained real-core fixtures.
2026-10-21 01:02:07 +02:00
LibretroAdmin f9629e019e WinRaw: ask Windows when the focus flag says unfocused
The focus flag follows the main window's WM_SETFOCUS and WM_KILLFOCUS.
Across a video driver restart that keeps the window - loading content,
a change of geometry - nothing is sent to the window, and a flag left
saying unfocused stayed so: every key and mouse button was dropped
until an Alt-Tab sent the window its focus again.

When the flag says unfocused, the poll now asks whether the main window
is the foreground window and goes by that for its frame. The flag
itself is still written only by the window procedure, so a loss it is
about to store is not overwritten. Nothing is asked while the window
has the focus.

winraw_poll_test checks that a stale flag with the main window in
front still reads the keys, and that a main window not in front does
not.
2026-10-21 01:00:17 +02:00
LibretroAdmin cf1274d604 win32: note the foreground when a window is left up
A window left up for the next video driver and then not taken by it -
a driver of another family, as when a core asks for Direct3D 9 after
the menu ran on Vulkan - is destroyed before its replacement exists.
Windows hands the foreground to some other program's window, and with
the content chosen on a controller it then refuses the new window the
foreground: the window came up in front but the keyboard went
elsewhere until Alt-Tab.

Leaving the window up now notes that it had the foreground, as
destroying it does, so the window the next driver shows takes it back
(win32_foreground_take_back()). A window that is taken and still in
front is not changed.
2026-10-21 01:00:14 +02:00
LibretroAdmin 98554f2896 Input: the hid joypad driver is kept across content loads
An input driver that holds nothing of the video driver's is kept
running across a video restart and a content load, and its joypad
driver with it - but the raw input driver kept itself only with its
own joypad driver, so with the hid one every content load closed every
pad and opened it again, a DualSense's lights set afresh.

The joypad driver now answers for itself (survives_video in
rarch_joypad_driver), as the input driver does: raw input's when it is
read by the poll, as before, and the hid one always - its pads are
read on its own threads or at the poll, and nothing of it is the video
driver's. The input driver is kept when both can stay. Raw input's
answer is now its own alone.

The frontend harness's kept-input lane has an input driver that can
stay with a joypad driver that cannot: both are restarted, together.
2026-10-21 00:42:37 +02:00
LibretroAdmin 68f6f53fb7 HID: no "Controller Disconnected" when the driver only restarts
Loading content restarts the joypad driver. The others stop quietly
and report their pads again when they start; the HID backends told
the frontend each pad had left as they stopped, so content started
with "Controller Disconnected" shown for a pad still in the user's
hands - and, with Pause on Disconnect on, the core paused.

The Windows, hidraw, libusb and GameCube backends now say a pad has
gone only when it has: a read failing, or the system saying so. When
the driver stops it lets its pads go without a word, as dinput, XInput
and the rest do.

winusb_hid_test and hidraw_hid_test hold free to that, and still see a
pad that leaves said to have left.
2026-10-21 00:42:34 +02:00
Codex c749b348fc media: leave validated WebM padding unbacked in prefix transfers
Let the exclusive prefix owner declare validated Void payloads permanently inert. Skip their whole interior pages without reading or committing them, preserve holes during backward refill, and keep byte budgets tied to actual reads. Include gather cursor demand when a metadata skip reaches an unavailable header.

Exercise interval growth and merging, boundaries, strict page faults, read counts, cap/EOF semantics and demux observer ownership. A maintained 96 MiB sparse fixture must match sequential pixels and PCM through replay while respecting encoded backing limits. Full core variants, native Windows, strict C89/Griffin and threaded lifetime checks pass locally.
2026-10-21 00:20:46 +02:00
Codex 01cd489eda tests: feed WebM hardware timestamp lookahead waits
The bounded timeline cursor can hold the last resident packet while waiting for the next header. Feed the fixture at that wait rather than requiring the decoder to clear its pending packet first. Cover the same transition after frame skipping, retaining exact decode/export and lease cleanup checks.
2026-10-21 00:17:34 +02:00
Codex dcb7feec3c video: admit complete WebM elements while seeking
The ordinary playback feeder honors a blocked WebM element span, but the seek feeder still stops at its fixed lookahead. A legal 12 MiB BlockGroup therefore leaves a seek pending indefinitely even though playback can cross it. Use the same declared span to size seek admission while preserving the existing bounded refill budget.

Compare target pixels with sequential decoding, replace the target while admission is incomplete, and close an exclusively owned session at that boundary. Run both feature configurations under ASan/UBSan, native Windows/GDB, strict C89 and TSan regression checks. Add the cases to regular gfx CI.

Allow the large-picture harness an explicit decode deadline. Valid 4K VP8 pictures larger than 8 MiB reach eight presented pictures in inline and threaded playback with a 21.6 MB encoded residency peak; the previous four-second threaded test deadline was too short for instrumented decoding.
2026-10-20 23:47:17 +02:00
LibretroAdmin 594117209f Windows: the hid driver reads pads on Windows' own HID driver
On Windows the hid joypad driver reached only pads bound to WinUSB -
the DualShock 3 - so a DualSense, DualShock 4 or any other pad with a
handler in input/connect needed its driver swapped first. It now also
opens them where Windows put them, through their HID interface, with
nothing to install or set up.

Each pad is read on its own thread with overlapped reads and handed to
the poll through the three buffers the WinUSB pads use. Writes never
wait: one goes at once, and one made while it is in flight waits
behind it, the newest replacing it, to go at a later poll. Output
reports go at the device's length, as Windows wants. A pad whose
reports have no ID has Windows' leading 0 taken off, so handlers get
the reports hidraw gives them. A pad on Bluetooth is said to be, from
its instance. hid.dll is loaded when the driver starts, as winusb.dll
is.

Beside another joypad driver (a DualShock 3 on WinUSB next to XInput)
these pads are left alone: that driver sees them already.

winusb_hid_test has a lane for it under Wine: a DualSense read and
lit, rumble waiting behind a write in flight and going once it is
done, a pad without report IDs read without the 0, Bluetooth told from
the instance, and the driver freed with a read and a write in flight.
Not yet run with a pad.
2026-10-20 23:46:42 +02:00
LibretroAdmin 0f8649c244 DualSense: rumble and lights through the shared report, Bluetooth too
The DualSense handler built its own USB output report and sent nothing
over Bluetooth. It now sends what raw input's driver sends for these
pads (input/common/sony_pad_output.h): over USB or Bluetooth as the
pad came, with Bluetooth's checksum, the player's lights at the first
report and rumble at the poll after a change. The motors are driven
the newer way on a DualSense Edge, which now has an interface of its
own, and on a DualSense from firmware 2.21, which its feature report
0x20 tells. Nothing is sent behind a Bluetooth stack's own header,
whose framing of output is that stack's.

A HID driver can now say a device is on Bluetooth (is_bluetooth): on
Windows every report comes at the longest one's length, so a
DualSense's 10-byte Bluetooth report cannot be told from USB's by it.

connect_parity_test holds the reports sent to the shared builder's,
over USB, over Bluetooth and on a padded report the driver says is
Bluetooth's, and checks the Edge and firmware 2.21 get the newer
motor control.
2026-10-20 23:46:38 +02:00
Codex 0797283e10 audio: carry bounded WebM read demand through the mixer
Publish complete-element admission requests while the mix owns the decoder, expose only an atomic play-serial-bound snapshot, and use it in live and prepared preview audio feeding. Include Opus in the published compressed-byte position. Keep the existing 256 KiB read budget and voice ownership protocol.

Clear exhausted partial PCM buffers before attempting a refill in both float and S16 paths. Otherwise a temporarily unavailable source leaves already-contributed samples marked available and replays them on the next mix.

Compare continuous Opus/Vorbis output with an independently prepared resident mixer, including real concurrent mix/feeder threads under TSan, serial rejection, refill ceilings and prepared seeks. Add a progressive-core fixture placing a 24 MiB group after the initial 512-picture prefix: all 636 pictures and PCM match the original on Linux and native Windows. Removing the explicit core demand predicate reproduces starvation at picture 540. Preserve native GDB, C89 and sanitizer evidence.
2026-10-20 23:35:00 +02:00
Codex cd8313883a webm: admit blocked elements beyond playback lookahead
A legal BlockGroup containing a key picture and 12 MiB of Void padding stalls windowed playback at picture 29: the complete group is needed for trailing metadata but ordinary lookahead never grows to its end. Preserve the coded picture unchanged and publish the blocked element span from the demuxer.

Feed that span over normal 512 KiB preview reads, expose it through the active video cursor, and keep progressive core filling while playback or gathering requests an element beyond the current prefix. Validate file-relative sizes before forming element-end pointers and clear requests on read success, rewind and seek.

Add a legal group fixture, laced partial-read/reset checks, actual inline/threaded playback and explicit per-refill budget assertions. Packet hashes match the original and FFmpeg decodes the fixture. ASan/UBSan, TSan, native Windows/GDB and strict C89 checks pass; the full builtin-player matrix also passes, including no-audio, no-MP4, threadless and Windows/Wine variants. This does not establish every independent audio feeder or hardware seek acceptance gate.
2026-10-20 23:35:00 +02:00
Codex d51909a9ff media: add shared fullscreen playback controls and bounded software seeking
Give Ozone, MaterialUI, RGUI and XMB a shared semantic transport bar,
with pause, seek, stop and timeline capture. Reuse the existing player
worker, coalesce seeks and prepare matching audio outside the mix path.
Keep texture and CPU presentation behind the same playback controller.

Add bounded MP4/WebM seek and timing cursors, Opus/Vorbis audio preroll,
and retained coded-byte windows across sparse WebM input. Separate the
read-demand frontier from the release frontier to prevent starvation
without copying a held coded packet or reading a padding gap in full.

Validate all four real menu drivers on Linux and Windows; exercise
software seek pixels, long timelines, sparse input, PCM, cancellation
and ownership with ASan/UBSan/TSan, C89 and stack-budget checks. Add
the corresponding maintained workflow coverage.

Hardware WebM seeking, oversized-packet admission, physical input and
display acceptance, and the remaining migration-plan gates stay open.
Input mapping/context/media-key infrastructure belongs to the separate
input migration; these controls consume existing semantic menu actions.
2026-10-20 23:35:00 +02:00
LibretroAdmin 9e5fd20d10 video_thread: ask the context again while it reads "not presentable"
Under the threaded wrapper the runloop's presentable answer is the one
the video thread published after its last frame. While it is false the
runloop sleeps a content frame ("Pacing: NoWindow") and asks again -
but the word only changes when another frame is drawn, so a swapchain
or presenter rebuilt since, or a window shown again, went unseen until
a frame happened to come, and with none coming the loop stayed in
NoWindow.

When the published word says not presentable, video_thread_presentable()
now also queues CMD_POLL_PRESENTABLE on the wrapper's lock-free deferred
ring, without waiting. The video thread, the word's only writer, asks
the context directly and republishes, so the runloop's next read is the
context's current answer whether or not a frame comes. A presentable
window costs the one atomic load it always did; the packet is only sent
on the path that is already sleeping a frame, and a full ring leaves it
to the next frame's answer as before.

samples/gfx/threaded_video: a new lane has the context say false for a
frame, then true, runs no frames, and requires the runloop's read alone
to bring the true answer within a second, with no frame run meanwhile.
It fails on the old wrapper ("still not presentable 1000 ms after the
context came back") and passes now.
2026-10-20 23:15:51 +02:00
LibretroAdmin a1bbf47dc7 DXGI: a busy target is not "nothing to present to"
Since c58d703a the DXGI bridge's presentable() also said false while
the GPU had not finished with the swap chain target the next frame
would use. The runloop reads false there as a window that is not
there - minimised, occluded, no swapchain - and with nothing else
holding the loop (vsync off) it sleeps one whole content frame and
shows "Pacing: NoWindow".

On a GPU-bound frame that is every frame. With threaded video the
video thread asks right after its own present, the moment the GPU is
furthest behind, and the answer stands until the next frame, so each
iteration ran the core and then slept a frame on top: flycast at a
high internal resolution (and after fast-forward, which fills the GPU
queue) dropped to under half its rate at 120 Hz with the overlay
reading Display+Driver+NoWindow, a 2 ms handoff and an 18 ms frame
fence, and stayed there for as long as the GPU stayed the bottleneck.
During fast-forward itself the nonblocking state keeps NoWindow out,
which is why it showed once fast-forward was let go.

presentable() is back to occlusion only, with a failed bridge still
answering true so its caller reaches the fallback. The wait for a busy
target moves to the producer, at the target, as a swapchain acquire
does: ra_dxgi_bridge_wait_target() reads the done fence and, only when
the target is still in use, waits on it with the bridge's event, a
second at most, and skips the frame on timeout. GL, which only queues a
GPU-side wait and so was what nothing else bounded, calls it before
writing the target. Vulkan needs nothing new: its acquire waits on the
same fence and its frame fences already bound how far the CPU runs
ahead. A frame the GPU keeps up with costs one fence read, as the old
check did; no wait, frame or present is added there, nor to a
fullscreen toggle.

samples/gfx/dxgi_present: a pending target stays presentable through
10000 asks without a probe or a wait (this fails on the old code), and
wait_target arms on the target's last use, times out without failing,
fails on a failed SetEventOnCompletion or a removed device, and costs
no wait when the target is free. The mock bridge gains the device
field ra_dxgi_bridge_device() reads since 72341019, which had stopped
the test compiling. Passes natively and under mingw-w64 + Wine.
2026-10-20 23:15:47 +02:00
LibretroAdmin ebf7d4edea ctr: load the bottom-screen textures through the driver's own load
The bottom-screen pngs are the driver's own textures, made from driver
code, so they go up through ctr_load_texture rather than back out
through the frontend's video_driver_texture_load. They are released
with ctr_unload_texture, which frees the texture with its data.
2026-10-20 23:11:41 +02:00
LibretroAdmin 2242d042a1 switch_nx: draw the input overlay
The software Switch driver blends the overlay page over the frame or the
menu in its framebuffer: each page is copied once at load into the
framebuffer's channel order, with every row's visible span, and drawn
with nearest sampling at the geometry, sub-rectangle, opacity and
full-screen setting the frontend sets, exactly rounded. In bilinear mode
it is drawn in the window the compositor scales up; behind the menu
when the setting says so. A page with nothing visible in a row costs
nothing there, and a disabled overlay one branch.

switch_free releases the frame and menu scalers.

Lane: samples/gfx/switch_nx_overlay includes the driver as it ships
against the libnx stand-in headers, and builds and runs on Linux,
mingw-w64 and aarch64.
2026-10-20 22:58:39 +02:00
LibretroAdmin 04240d63f1 HID: a native DualSense handler
The DualSense and DualSense Edge (054c:0ce6, 054c:0df2) had no pad
handler, so the hid joypad driver could not use them. connect_ps5
reads all three shapes of their input report - USB's 64 bytes,
Bluetooth's 10-byte report 0x01 and its full report 0x31 - from where
it lies, keeping the buttons, sticks and trigger travel, and skips a
Bluetooth stack's transaction header when there is one.

The buttons are the RetroPad's as for the DualShock 4, PS also opens
the menu, and PS, the touchpad's click and mute are buttons 16 to 18
for binding. The sticks are axes 0 to 3 and L2 and R2's travel axes 4
and 5, with no dead zone of its own: the frontend's applies.

Over USB the first report lights the player's lights and lightbar, and
rumble is sent from the poll when it has changed, at most one report a
poll however often a core asks. Nothing is sent over Bluetooth: its
output reports need a checksum and the Bluetooth backends frame them
differently.

connect_parity_test holds it to the reports' layout on 40000 USB and
Bluetooth reports, and checks the lights are sent once, rumble only at
the poll and nothing over Bluetooth. Not yet run with a pad.
2026-10-20 22:47:59 +02:00
LibretroAdmin 97ca068ba5 Kade, Retrode: read the report where it lies
The Kade encoder copied each report into a 64-byte buffer, whatever
its length - a longer one ran past it - and the Retrode copied each
port's report into one of its four. The Kade now decodes the report it
is handed and keeps only its buttons; a report too short to hold them
(3 bytes) is left alone. The Retrode keeps each port's buttons and two
stick bytes; it no longer needs to turn away reports longer than its
buffer. button() answers 0 or 1.

connect_parity_test holds both against the copying handlers on 20000
random reports of random length - the Retrode's on all four ports and
none - and every button, the set and every axis agree. The reference
agrees with the old handlers on all of them.
2026-10-20 22:28:30 +02:00
LibretroAdmin 8953f30210 DragonRise, HORI mini: read the report where it lies
The DragonRise encoder copied each report into a 64-byte buffer and
the HORI mini wired PS4 pad into a 512-byte one, whatever its length -
a longer one ran past it - and both read the sticks back out of that
copy at every axis read. They now decode the report they are handed
and keep only the buttons, the stick bytes and, for the DragonRise,
whether it is in analog mode. A report too short to hold what is read
(7 bytes for the DragonRise, 8 for the HORI mini) is left alone.
button() answers 0 or 1. The HORI mini's pad struct shrinks by 508
bytes.

connect_parity_test holds both against the copying handlers on 20000
random reports of random length, with the DragonRise's analog mode
coming and going: every button, the set and every axis agree. The
reference agrees with the old handlers on all of them.
2026-10-20 22:17:17 +02:00
LibretroAdmin ab9b6a54ce SNES/NES USB pads: read the report where it lies
Both handlers copied each report into a 64-byte buffer, whatever its
length - a longer one ran past it - and read the stick bytes back out
of that copy at every axis read. They now decode the report they are
handed and keep only the buttons and the two stick bytes. A report too
short to hold what is read (7 bytes) is left alone. button() answers 0
or 1.

connect_parity_test holds both against the copying handlers on 20000
random reports of random length: every button, the set and every axis
agree. The reference agrees with the old handlers on all of them.
2026-10-20 22:09:12 +02:00
LibretroAdmin be9026bf62 Input: several ports may share one controller again
Setting two ports to the same device index lets one pad act as two:
it is how GoldenEye and Perfect Dark get their two-stick control
styles with one controller, and how twin-stick games that read two
ports are played (#19875). Since July that no longer worked. The menu
swapped the other port away when one was stepped onto its pad - with
"All Users Control Menu" off this could leave port 1, the only port
that drives the menu, without one - and the mapping was "repaired" to
one pad per port when it was read, on every hotplug and after a
driver restart.

A repeated index is now the user's choice and is kept everywhere:

- The menu changes only the port being edited. A port on another
  port's pad says so ("shared with Port 2"), so a share made by
  accident is seen rather than silently undone.
- Reading the configuration puts right only an index out of range.
- A hotplug and a driver restart move the lowest port on each pad, as
  before, and the ports sharing its pad follow it. A pad that comes
  back in a slot no port reads is given to its port without
  displacing anyone.

Nothing runs per frame. The frontend harness has a lane for it: an
override's 0 / 0 survives reading and saving, a menu step moves one
port, a reserved pad arriving and a driver restart reporting the pad
elsewhere keep the pair together. With the old menu swap put back the
lane fails. device_registry_test has the unread-slot case.
2026-10-20 21:53:28 +02:00
LibretroAdmin 8b595b232b gfx_surface: post compressed stills to the video thread
Under the threaded wrapper a still given only a GPU-native payload the
driver samples - a menu icon or badge decoded from a DDS - is posted as
an async load, the image the surface's until the completion, the same
way the pixel stills go. A format the driver does not sample keeps the
plain load, which decodes. gfx_surface_submit_compressed() shares the
post.

Lane: samples/gfx/gfx_surface_still posts a BC1 still, frees one with
its load still queued, and loads one the driver does not sample.
2026-10-20 21:51:56 +02:00
LibretroAdmin 1dd09770e5 SET_HW_RENDER: grant GLES versions the device can give
GLES requests are checked against one ceiling: the device's own
ro.opengles.version on Android, the build's GLES level elsewhere or
when the property cannot be read. The regular Android flavors now
grant OPENGLES_VERSION 3.1 and 3.2 on devices that have them, so a
core probing down from 3.2 stops at the version the context really
gives, and a device below a requested version still refuses it and
lets the core fall back. GLES2 builds take 3.0 as their ceiling, the
level they already grant through OPENGLES3.

samples/runloop/hw_context_request drives the request function under
a GLES2 build with the property stubbed, a GLES3.2 build and a GLES3
build.

Refs #19894
2026-10-20 21:49:42 +02:00
LibretroAdmin 963ae53ed1 SET_HW_RENDER: log refused context types as warnings
A core asks for one context type after another until the frontend
accepts one, so a refusal is the expected answer to a probe. Each one
is now a warning naming the type and version that was refused, and
the environment handler no longer prints the driver ident ("gl") for
every GL and GLES request. GLES2-only builds name OPENGLES_VERSION
requests instead of reporting them as an unknown context.

Refs #19894
2026-10-20 21:32:05 +02:00
LibretroAdmin 5ca54f058f Vulkan: wait for the frame slot when no swapchain can be made
A minimised window has a 0x0 surface, so no swapchain is made for it,
but frames are still drawn: each one re-records its slot's command
buffer and rewrites its buffers and descriptors. The acquire now moves
to the next frame slot and waits for its fence in that case too, so a
slot is never drawn into while the GPU is still reading it.

samples/gfx/vulkan_minimized_frames links the real vulkan_common.c and
draws frames against a 0x0 surface, checking that each slot's last
submission was fenced and waited on before it is used again.

Fixes #19877
2026-10-20 21:17:07 +02:00
LibretroAdmin f7927114aa Overlays: upload DDS images as their BCn blocks
An overlay image loaded as a BC1/BC2/BC3/BC7 payload goes up as its
blocks when the driver samples the format, a quarter to an eighth of
the VRAM and upload bytes of RGBA8. One the driver does not sample, and
every image of a page shown through load(), is decoded once, and the
pixels are shared by every page entry that names it.

gfx_surface_submit_compressed() posts the payload to the video thread
the way pixel stills are posted, without a round trip. The wrapper's
async load takes a payload-only image through load_texture_compressed
and answers 0 for a format the driver does not sample. A pack released
with such an upload in flight hands the payload to its surface
(gfx_surface_free_adopt_payload), as it does pixels.

Lanes: samples/input/overlay_textures loads a real DXT1 file through
rdds - sampled, decoded, declined, through load(), and threaded with
the pack freed mid-upload - and builds and runs under mingw-w64;
samples/gfx/threaded_video checks the wrapper's compressed load.
2026-10-20 21:10:53 +02:00
LibretroAdmin 90e986eb24 PS2/PSX adapters: read the report where they lie
Both adapter handlers copied each report into a 64-byte buffer,
whatever its length - a longer one ran past it - and read the sticks
back out of that copy at every axis read. They now decode the report
they are handed and keep only the buttons, the four stick bytes and,
for the PSX adapter, whether the pad is in digital mode. A report too
short to hold what is read (8 bytes for the PS2 adapter, 7 for the
PSX one) is left alone, where the PS2 one read past a short report.
button() answers 0 or 1 and no longer shifts into the sign bit.

connect_parity_test holds both against the copying handlers on 20000
random reports of random length, with the PS2 adapter's other ports
and the PSX adapter's digital mode coming and going: every button,
the set and every axis agree. The reference agrees with the old
handlers on all of them.
2026-10-20 20:52:27 +02:00
LibretroAdmin 9c58498eec Wii U GC adapter: read the report where it lies
The adapter handler copied each report into a 64-byte buffer, whatever
its length - a longer one ran past the buffer - and then copied each
port's nine bytes out of that before decoding them. It now decodes the
four ports straight from the report it is handed and keeps only the
buttons and sticks it answers from; a report shorter than the 37 bytes
an adapter sends is left alone. Nothing the handler answers changes.

connect_parity_test checks all four ports against the copying handler
on 20000 random reports with ports powered, connected and a WaveBird
coming and going: every button, the set, every axis and each port's
state agree. The reference agrees with the old handler on all of them.
2026-10-20 20:32:13 +02:00
LibretroAdmin 21b4c80736 PS4: decode the input report once, where it lies
The DualShock 4 handler copied each report into a bitfield struct and
worked every button out of it again at every read: get_buttons went
through sixteen tests and the hat four times a call. It now decodes
the report once, when it comes, into the RetroPad's buttons as one
word, the PS button and the four stick bytes, read from the report
where it lies by byte and bit rather than through bitfields, so it is
the same on either byte order. get_buttons is a copy of that word and
button() a bit test. A report too short to be one is now left alone;
the old copy read nine bytes past the ID whatever its size.

connect_parity_test holds the handler to the copying one: a reference
built from the old struct and reads, given the same reports - every
hat value, every bit alone and 20000 random ones - must agree on every
button, the whole set and every axis. The reference agrees with the
old handler itself on all of them. CI runs it under ASan and Wine.
2026-10-20 20:17:51 +02:00
Codex 7587b54742 video: retry software playback while input is unavailable
Keep software inline and worker playback alive when the stream reports a temporary input or decoder-capacity wait. End an inline slot write before retrying on a later poll. Hardware playback already distinguishes this state from end-of-stream.

Reproduce early termination after two pictures in canvas, inline-slot and worker-slot modes. With the fix, all five pictures arrive in order. Validate under ASan/UBSan, TSan, threadless builds, native Windows/GDB and strict C89 stack checks. The asynchronous seek implementation remains separate work.
2026-10-20 20:05:09 +02:00
LibretroAdmin 0c7b7a0c6a Input: what a command holds is given to the core exactly
R4 asked whether input held from a command (SET_INPUT, a RUN_FRAMES
press) should go through SOCD cleaning and D-Pad rotation, and what a
hold means under netplay.

It stays exact: SOCD cleaning and rotation are for a person's hands on
a pad, and a script says what the game is to see - a TAS-style left
and right together, or up with the picture turned, means just that.
Under netplay a hold is the player's on this machine: netplay samples
the local device through the same reads, so what is held is sent like
a press and every peer runs the same frames.

That is now written where it is asked - SET_INPUT's description, which
the command list and the MCP tools show, and the handler - and the
frontend harness holds left and right with SOCD set to neutral and the
D-Pad turned, and checks the core is given both, unturned.
2026-10-20 20:03:20 +02:00
Codex 8947d5454e overlay: keep atlas texture declarations C89 compatible
Move declarations to the beginning of their blocks so the two-frame atlas path builds with C89_BUILD=1. The complete threadless frontend and inline/threaded overlay texture sanitizer tests pass.
2026-10-20 19:45:21 +02:00
Codex 32d71a3e14 media: clock thumbnail playback from mixed PCM
Publish per-play mixer sample counts and pace thumbnail video from actual mixed PCM, retaining wall-clock fallback and pause behavior. Begin audio after the first accepted picture. Retain the audio window independently of mixer retirement, bind the session to a voice generation, and reject stale buffer bounds after voice reuse. Update all driver-harness mixer stand-ins for the clock API.

Validate exact float/S16 PCM continuity, codec loops, stale play bounds, AAC starvation and retirement under ASan/UBSan, concurrent mixer access under TSan, C89 stack limits, a complete threadless frontend link, and native Windows frontend/video/PCM/loop checks under GDB. All thirteen audio-driver harnesses link; mixer-lock and pipeline-wakeup runtime checks pass. This is a mixed-PCM clock, not a physical device cursor; asynchronous seek and fullscreen controls remain separate work.
2026-10-20 19:43:53 +02:00
LibretroAdmin c7d337f0bd Overlay: a two-frame APNG's press uploads nothing
A two-frame APNG is an unpressed/pressed pair. Each press and release
copied a whole frame into the image's slot and uploaded it to its
texture.

Both frames are now one texture, stacked, with the first frame's
last row and the second frame's first row repeated between them so
filtering at either edge never reaches the other frame. A page that
shows the image is pointed at the frame it shows with tex_geom, and
a press only moves that rectangle: no copy, no upload, no allocation.
Once the texture is in, the stacked frames are freed like the rest of
the pack's pixels.

It is used on drivers with load_textures and tex_geom, while the
stack is no taller than 1024 rows; otherwise the per-press upload
stays as it was.

overlay_textures_test checks the texture is the stack, the page
points at the first frame, a press moves the rectangle to the second
and uploads nothing, and the stack is freed, directly and under the
threaded wrapper. overlay_apng_press_on_screen_test runs the real
retroarch under Xvfb and checks the square is all red at rest, all
green while held and red again after, sampled at its top, middle and
bottom, threaded and not.
2026-10-20 19:40:43 +02:00
Codex ae6ae5ee26 audio: update mixer pause stubs in driver harnesses
The new audio-driver pause wrapper references audio_mixer_voice_set_paused. Thirteen driver harnesses stub the mixer rather than link it; add that symbol to every stand-in so their full driver translation units link again. Production code is unchanged.

Reproduce the published pipeline_wakeups sanitizer link failure, then build and run all thirteen affected harnesses under ASan/UBSan. Use the established PACED_ONLY clocked-pipeline CI mode and SDL dummy-device mode, with an explicit non-desktop D-Bus address for the headless local run. Retain the initial too-short benchmark timeout and external libdbus leak logs as failed evidence.
2026-10-20 19:01:18 +02:00
Codex 52af5898dd media: retain thumbnail video and PCM across transport pause
Add a main-thread thumbnail pause command. Video retains the displayed picture and its remaining duration; mixer voices retain compressed decoder, buffered PCM and resampler state while paused. Deferred audio does not begin until resumed. This provides the playback primitive for the future fullscreen transport UI without changing input routing.

Validate inline/threaded/threadless video scheduling, exact float/S16 PCM continuation with equal-rate and sinc-resampled streams, TSan concurrent mixer requests, real thumbnail/session and audio feeder sanitizer tests, strict C89 stack checks and the native Windows frontend plus GDB-run unit tests. Seeking, a shared presentation clock and input/UI integration remain separate work.
2026-10-20 18:45:15 +02:00
Codex 32e9d5c1aa media: isolate HDR tone tables and preserve signalled peak
Replace shared mutable HDR lookup tables with immutable defaults and stream-owned prepared PQ curves. Concurrent streams at different peaks no longer race. Preserve MaxCLL and mastering maximum through CPU MP4/WebM conversion using the same peak-selection rule as GPU import.

Validate independent HDR math, default table equivalence, concurrent ASan/UBSan/TSan, strict C89 stack limits, coded peak metadata and full player/AV1 suites. Thirty native D3D11/D3D12/Vulkan direct and threaded recovery cases cover 360 pictures (maximum RGB error 1) and 1559934 PCM frames (maximum S16 error 3) without timing alignment.
2026-10-20 18:33:03 +02:00
libretroadmin 790cc02496 retro_atomic: cast the value in the C++11 backend's free-function macros
The C++11 backend hands the value argument straight to std::atomic_init,
std::atomic_store_explicit and the std::atomic_fetch_*_explicit templates.
Before P0558R1 (libstdc++ 8 and older) those deduce the element type from
the value as well as from the pointer, so a C++ core that passes an
enumerator or an unsigned, as parallel-dc does, fails to deduce and does
not build on GCC 8 (libretro/parallel-dc#1215). The macros now cast the
value to int, as the size_t ones already do.

The retro_atomic_cxx11_old_stl sample gives every value macro an
enumerator or an unsigned, runs against the host <atomic>, and is parsed
again against a stand-in <atomic> with the pre-P0558R1 signatures, so the
failure shows on any compiler; the samples job runs it.
2026-10-20 18:14:27 +02:00
Codex 09b90bc905 media: preserve VP9 HDR color and playback through decoder replacement
Treat absent and explicitly unspecified HDR matrices alike in the CPU
SDR8, SDR10 and scRGB converters. Admit supported ten-bit HDR WebM VP9
streams and BT.2020 VP9 decoder headers on D3D11 and Vulkan.

Select the new driver's packed ten-bit input format from its configured
capability before an instance exists. Cold starts otherwise upload
XRGB2101010 software frames into an eight-bit texture.

Resume WebM decoder references by display ordinal without replaying a
visible seek picture, resetting the wall clock or rewinding PCM. Reuse
the software presentation path while a replacement GPU decoder catches
up; preserve current pictures without extending its time budget or
adding production waits. User seeks and state restore clear that reuse.

Extend fixed-ordinal native recovery checks and reusable PQ/HLG fixtures
for VP9, including an unspecified container matrix with unchanged coded
packets. Keep the existing MP4 replacement-hold oracle.

Validation: matrix/default and source-primary sanitizer tests; full
builtin-player normal, no-audio, WebM-only, threadless and Wine host;
full AV1 ASan/UBSan suite; metadata/framebuffer tests; strict C89 with
4 KiB core and 2 KiB library stack limits; final seek/restore regression.
33 actual D3D11/D3D12/Vulkan native cases pass 396 pictures and 2,702,045
fixed reference points (max RGB error 1/255). Their 1,716,021 PCM frames
match independently decoded audio within 3 S16 units at zero alignment.
2026-10-20 18:06:16 +02:00
Codex 631c845c4b media: preserve source primaries in CPU HDR fallback
CPU HDR conversion assumed BT.2020 even when the source explicitly signalled BT.709. Actual coded PQ playback matched the GPU reference within one RGB unit, then diverged by 83-112 units during fallback.

Add source-primary-aware SDR8, SDR10 and linear scRGB blits, retain the existing entry points with their BT.2020 defaults, and forward primaries through MP4/WebM band contexts and builtin VP9 presentation. BT.709 skips gamut conversion and uses BT.709 luminance weights for linear HLG output. Core HDR ABI and existing tone curves remain unchanged.

Extend reusable coded fixtures and independent references for explicit BT.709 primaries. Test compatibility wrappers, both ranges/transfers/primaries, standalone peak handling and linear output.

Validation: old implementation with a test-only ABI adapter fails the new references. Eighteen actual D3D11/D3D12/Vulkan recovery cases pass 216 pictures and 1,763,038 reference points; 96 CPU MP4/HEVC and WebM/VP9 pictures pass 684,802 points, all within one RGB unit. Full builtin-player and AV1 sanitizer suites, strict C89 stack limits and byte-identical legacy oracle/default fixtures pass.
2026-10-20 17:33:08 +02:00
LibretroAdmin 99c4a9fa10 OpenXR: a stopped writer holds no reader of the frame's state up
The frame's shared state - tracking, the screens, the quads and the
projection - went through seqlocks whose readers spun while the stamp
was odd: a writer preempted part way through a publication held the
XR thread, the drawing thread or the input poll until it ran again.

They are now latched seqlocks: two copies of the words, the writer
storing one and then the other, the readers reading the copy that is
not being stored. A reader only starts over when the writer has got
through a whole copy during its read - the writer going on, never
waiting for it. There can still be several readers, which a three-slot
exchange would not allow.

openxr_core_test stops a writer half way through a publication and
checks the reader goes on at once with the last whole one (the old
reader never returned), and runs a writer against two readers checking
no read is mixed or goes backwards; it passes under TSan.
2026-10-20 17:17:07 +02:00
Codex 0735138eed media: admit HDR movie imports and retain fallback color precision
The MP4 hardware gate still rejected HDR based on a pre-metadata export contract. Admit supported PQ/HLG metadata and retain importer refusal/software fallback. Test HEVC and AV1 metadata transport, unsupported color rejection and software recovery.

Preserve fractional encoded RGB through the CPU HDR transfer lookup using Q16 matrices and interpolation. Coarse integer conversion lost color precision near gamut boundaries. Keep SDR and linear scRGB behavior and the existing HDR curves unchanged.

Add reusable coded Main10 PQ/HLG fixtures and isolate the native sample from live hotkeys by temporarily disabling logical input ports. Explicitly select SDR output for the numerical presentation oracle.

Validation: controlled old-code failures, ASan/UBSan color and complete AV1 suites, full builtin-player workflow and strict C89/2 KiB stack checks pass. Twelve native coded HDR and eight focused resize/fullscreen cases pass across D3D11/D3D12/Vulkan and direct/threaded paths: 240 pictures, 1,809,740 reference points, maximum RGB error three. Separate CPU coded captures differ by at most one RGB unit. Physical HDR output remains separate acceptance work.
2026-10-20 17:10:12 +02:00
Codex dca4a2ea61 samples: exercise movie resize and fullscreen recovery
Add selectable steady, resize and borderless fullscreen modes to the actual movie recovery probe. Assert changing viewport sizes, fullscreen window/monitor bounds, threaded-wrapper state and restored geometry after driver replacement. Keep fixed picture ordinals and comparison tolerance, caching only the reference flat-point mask.

Save core PCM and per-tick callback/runloop/menu/focus diagnostics. Queue drains and cached redraws remain test-only; no global display or HDR state is changed.

Validation: all 36 current-master Main8 limited/Main10 full cases pass across D3D11/D3D12/Vulkan, direct/threaded, steady/resize/fullscreen. An older loss-of-core-progress case has not recurred; these runs do not establish its cause.
2026-10-20 16:56:07 +02:00
Codex 9d963ae8fb media: resume progressive audio after temporary packet starvation
Initialize demuxed AAC and Opus from valid setup before their first packets arrive. Keep the core decoder alive across a growing input prefix and use its actual rate for silence cadence. Separate AAC refill readiness from pending sample counts so a two-frame tail cannot masquerade as a resident wall. Retire the MP4 audio gather if setup fails.

Add metadata-only setup, malformed source, S16/F32 two-frame tail, and real delayed AAC 48/44.1 kHz and Opus full-track tests. The new fixtures preserve decoded video while placing audio beyond the initial fill budget.

Validation: old backend/core/status and fixed-rate silence controls reproduce the defects. ASan/UBSan, full builtin-player audio/noaudio/WebM/threadless/Windows-host workflow and strict C89 stack checks pass. Eighteen native D3D11/D3D12/Vulkan direct/threaded layout cases produce identical PCM (FFmpeg fixed-alignment maximum S16 error one) and 216 reference-checked pictures.
2026-10-20 16:39:01 +02:00
Codex 5985f598d5 av1: test owned MP4 configuration lifetime
MP4 decoder setup is now owned by the demuxer so progressive input pages can be discarded before decoder restart. Replace the stale borrowed-pointer assertion with byte and lifetime checks after overwriting the original av1C record. Keep the Matroska borrowed-configuration and both containers borrowed-packet checks.

Validation: reproduced the original assertion failure; full AV1 ASan/UBSan suite and all seven coded-fixture generators pass via tools/ci_local.py.
2026-10-20 16:32:21 +02:00
LibretroAdmin da66aeb1f4 WinUSB: memmove for the busy list's swap
The overlap-copy check flags a copy within one array whose source and
destination it cannot tell apart. The two entries are never the same
here, but memmove says so without an exception for the checker.
2026-10-20 16:27:53 +02:00
LibretroAdmin 2b4bc98676 Input: hand the sensor snapshot over in three slots, not a seqlock
The shader backends read gyro, accelerometer and rest each frame from
the video thread. They were published under a seqlock, whose reader
started over - spinning - while the poll was mid-publication, and
whose nine floats went through nine atomic stores and loads.

They are now three slots of nine floats: the poll writes its own and
hands it over with one exchange for the middle slot; the reader, when
the middle is new, exchanges its slot for it and reads the floats
where they lie. Neither side ever waits or retries, and a read never
mixes two polls. The exchange lives in input_sensor_snap.h so a test
can drive the same code.

sensor_snap_test publishes as fast as it can from one thread while
another reads, and checks no read is mixed or goes backwards and the
newest is read once the writer stops; CI runs it under ASan, TSan and
Wine. The frontend harness's idle lane counts publications instead of
the sequence.
2026-10-20 16:27:49 +02:00
LibretroAdmin 054ecb461c DS3: decode the input report where it lies
The DualShock 3 handler copied each 49-byte input report into the pad
and decoded it there, keeping the copy so the pressure axes could be
read from it later. It now decodes the buttons, sticks and the twelve
pressures straight from the report it is handed - the backend's own
buffer for hidraw and WinUSB - and keeps only those: 12 bytes written
per report instead of 49 copied, and 52 fewer bytes per pad. Nothing
the handler answers changes.

hidraw_hid_test now checks every pressure axis against its report
byte, and that a report that is not the input report leaves them; it
passes on the copying handler and on this one alike.
2026-10-20 05:50:37 +02:00
LibretroAdmin d0a696916e OpenXR: no new pull across a session restart; rumble kept together
A trigger held while the session restarted counted as a new pull: a
session's start cleared every trigger as let go. It now starts as
after a loss of focus, with no pull until the trigger is let go.

A pad's strong and weak rumble were two atomics, so the haptics read
could pair one call's strong with another's weak. They are now one
word, strong in the high half and weak in the low, each set by a
compare-and-swap that keeps the other.

Rumble may be asked for from any thread, but it found the pad by the
main thread's slot array. The slots are now also published as one
word whenever the main thread changes them, and rumble and
input_openxr_has_slot() read that.

openxr_input_test holds a trigger across a session restart and checks
it presses nothing until let go and pulled again; openxr_haptics_test
checks both motors are kept in the one word.
2026-10-20 05:40:35 +02:00
LibretroAdmin 085185385a WinRaw: read the window's focus once a poll
The poll and the reads of its frame each loaded the focus flag on
their own - up to five times in a poll, and again in every key and
input_state read - so a focus change part way through could have one
frame act on both values: the mouse buttons taken as unfocused while
the keys still read as focused, or the focus-loss key clear and the
cursor sync seeing different edges.

The poll now reads it once, at its start, and the poll and the
frame's key and input_state reads go by that. The window procedure's
own check, which decides per report whether background input is
taken, still reads it live.

winraw_poll_test checks that a focus change between polls does not
change what the frame reads, and that the next poll goes by it.
2026-10-20 05:31:00 +02:00
Codex 81ffc562cf mp4: retain decoder setup after streaming page discard
Codec configuration records borrowed moov bytes. In a fast-start movie, bounded streaming releases those pages before a backend replacement rewinds and opens the decoder again. Native HEVC recovery then faults while reading hvcC.

Keep decoder configuration with the demuxer's owned sample tables, and release it at close. Opus retains its existing inline buffer. Reject a track if its configuration cannot be allocated. Packet payloads remain borrowed; copying occurs only while parsing track setup.

Exercise H.264, HEVC and AV1 configuration lifetime and allocation failure alongside the existing fragmented MP4 checks. Native discard/refill recovery and an old/new controlled comparison cover the original crash.
2026-10-20 05:29:24 +02:00
LibretroAdmin 9fc70fe141 WinRaw: a mouse click shorter than a frame is not lost
The mouse button word recorded only whether each button was held, so
a click pressed and let go between two polls never reached the
frontend. Each press now also sets a latch in the upper half of the
same word, in the same compare-and-swap; the poll reads what is held
or was pressed since the last poll and takes the latch. A poll with
nothing latched is the one load it was.

winraw_mouse_buttons_test checks a short click reads as down once and
then up, a held button stays down, and losing focus clears the latch;
it runs as before under TSan and Wine.
2026-10-20 05:18:26 +02:00
LibretroAdmin 64b3ddd33f Input: let go of held keys when the input driver goes
When a video restart freed the input driver, a key held at that
moment never got its release: the driver that would have reported it
was gone. The core's keyboard callback and the menu kept the key held
until it was pressed and let go again; polled keyboard state was not
affected.

The keys given out down are already kept for the key lane (and now
also without threads). When the input driver is freed with the video
driver, or a kept driver is dropped, whatever was still on its way is
taken first and every key still down is given out as released. A
driver kept across the restart is left alone; it reports its own
releases.

A frontend harness lane presses a key, restarts the video and input
drivers, and checks the core is given the release once.
2026-10-20 05:18:21 +02:00
Codex 35249dfb88 video: preserve movie geometry on NULL repeats
A refused movie import can leave the previous GPU image active without a pending movie lease. The CPU-slot row clamp then shortened a NULL duplicate to the unrelated CPU allocation, intermittently failing the threaded movie-ring lane. Cached replay had the same clamp.

Clamp only submissions carrying CPU pixels. Exercise accepted, busy and refused imports followed by ordinary and worker-side cached repeats, and report geometry separately from ownership failures. No frame-path synchronization or HDR interfaces change.
2026-10-20 05:11:53 +02:00
Codex e1737cfcdd d3d11: reject readback extents changed by cached redraw
A screenshot destination is sized from the viewport before readback.
A pending window resize can be applied by the cached redraw inside the
D3D11 readback function, which then copies the larger extent into that
old destination. Native threaded movie resize testing hit a segmentation
fault in the SDR copy path.

Reject a changed extent before mapping or writing the destination. HDR
readback also redraws before acquiring a backbuffer, so it neither holds
a resize-blocking reference across redraw nor copies changed dimensions.
Stable screenshots and nonblocking recording retain their existing paths.
No production frame wait, lock, readback or pacing log is added.

Add strict-C89 extracted-source regression probes to Linux graphics CI.
The SDR probe covers destination canaries through growth, shrinkage and
reshaping, stable copies and recording map flags. The HDR admission probe
covers redraw/acquisition order and changed extents. Both old-source
negative controls fail at the intended runtime assertions.

Validation: portable GCC and native Windows/GDB probes pass. Actual coded
movie resize and borderless-fullscreen cases pass across D3D11, D3D12 and
Vulkan, direct/threaded: 144 pictures and 968912 reference points, maximum
channel error 3, matching PCM totals. Native D3D11 SDR and scRGB brightness
100/200 checks pass 60 captures through resize. Physical display luminance
and audio-device timing remain separate acceptance gates.
2026-10-20 05:01:58 +02:00
LibretroAdmin adf77c0f89 Vulkan (Windows): restart for a refresh rate change on the main thread
In exclusive fullscreen, changing the refresh rate makes the Windows
Vulkan context restart the video and input drivers from its
check_window. With threaded video that runs on the video thread, in
the driver's alive(), so the input driver could be torn down while
the main thread was polling it; without threaded video it ran inside
the video driver's own call.

The restart is now asked for with command_event_later(), which any
thread may call: the command is carried out on the main thread when
it next retires finished tasks, a frame later.

A frontend harness lane asks for a command from another thread and
checks it is done on the main thread, not on the thread that asked.
2026-10-20 04:57:58 +02:00
Codex 4f377e67d8 cores: validate threaded Main10 and full-range movie recovery
Extend the native movie recovery harness with an explicit threaded mode
and assert the requested wrapper state before and after driver replacement.
Generate Main/Main10 limited/full-range fixtures with verified coded profile,
bit depth, colour range and BT.709 metadata.

Enable vsync for synchronous Windows Vulkan capture and pace the null-audio
fixture at the core's reported frame rate. Unthrottled execution can exhaust
decoder capacity, while Windows mailbox emulation can leave no image for a
synchronous readback. These waits are confined to the sample, not production.

Generate the independent RGB reference with explicit bilinear chroma and
accurate full-precision rounding. Default bicubic interpolation overshot a
saturated Main10 boundary: independently decoded YUV maps to green 38.55,
both production paths yield 39, but the old reference yielded 35. Retain the
three-value error threshold and fixed ordinal oracle. Input movies are
byte-identical across the reference correction.

Validation: 24 native direct/threaded driver/profile/range combinations,
288 pictures and 1951898 independent reference points pass within three
channel values. All normal GDB runs retain 48766 PCM samples. The baseline
captures are also reanalysed against the corrected, byte-identical-input
reference. Physical audio timing and HDR movie output remain separate gates.
2026-10-20 04:48:49 +02:00
Codex 99b2adc2b4 media: retain playback continuity while a decoder restarts
Vulkan decoder catch-up can need another run after replacing the video
backend. The pending-restart branch skipped playback-clock advancement
and submitted only a duplicate callback to a renderer with no cached
picture. This blanked the replacement window and delayed following
pictures relative to uninterrupted playback.

Advance wall time during catch-up and resubmit the retained CPU picture
when available. The ready decoder can then catch up to the current fixed
ordinal without resetting PCM or reopening the backend each run.

Extend the portable ownership test through a busy retro_run, checking the
retained picture and clock. Extend native movie recovery to Vulkan and
verify the explicitly reported replacement hold, followed by immediate
ordinal catch-up. Give child processes an explicit null stdin on Windows.

Validation: full builtin-core local CI passes ASan/UBSan, audio-disabled,
WebM-only, threadless and MinGW/Wine lanes. Native D3D11/D3D12/Vulkan
pass 36 presented pictures and 249072 independent reference points with
maximum channel error 3 and unchanged audio totals. Strict-C89 frontend
link passes. The old core fails both new portable runtime assertions.
Null audio does not establish physical device A-V timing.
2026-10-20 04:37:36 +02:00
Codex 1ab17f736c vulkan: keep HDR game transfer opaque
An SDR XRGB8888 core may leave its unused alpha byte zero, and a
shader preset may preserve that byte. The HDR game transfer used
the UI source-alpha blend pipeline, turning otherwise valid game
RGB black. Give the game transfer an opaque pipeline while keeping
the existing blended HDR pipeline for translucent UI composition.

Add a native frontend conformance sample covering SDR controls,
HDR10/scRGB at 100/200 nits, one/two identity shader passes, an FP16
SDR-owned target and a translucent input overlay. Assert active
preset pass counts and compare captured patches with independent
transfer and gamut references.

Validation: 21 native cases / 252 captures and the full compile
matrix pass. Restoring only the old game pipeline binding makes
the quantitative preset case fail (0 vs 146.53 nits). Windows HDR
visual saturation report was separately closed by the user as a
configuration mistake; brightness defaults and shader math stay
unchanged. Physical display luminance is outside these captures.
2026-10-20 04:29:48 +02:00
LibretroAdmin 68b94be53a Input: command holds do not apply under achievements hardcore
SET_INPUT refused new holds under hardcore mode, but a hold made
before hardcore was turned on stayed applied, so a command could
still press buttons for the core in hardcore. Holds are now ignored
while hardcore is active and let go at the next poll, so they do not
come back when hardcore ends. Without holds nothing extra is checked.

command_hardcore_test runs the real input_driver.c: a hold made
before hardcore does not reach the core under it, the poll lets it
go, and it is gone after hardcore ends.
2026-10-20 04:29:05 +02:00
LibretroAdmin 7b7e2749af Input: enable DualShock 3 support on Windows from the menu
Windows' own HID driver cannot start a DualShock 3, so the pad showed
up as configured but no button worked. RetroArch now says so when
such a pad connects - "needs enabling: Information > Input
Information > Enable DualShock 3 Support" - instead of reporting it
as configured.

That entry, under the pad in Input Information, moves every
DualShock 3 plugged in to Windows' own Microsoft-signed WinUSB driver
(winusb.inf's "WinUsb Device"), after which RetroArch's WinUSB source
starts it. Under a pad already on WinUSB, "Restore Windows Driver"
gives it back to the driver Windows chooses itself. No driver of
RetroArch's own is installed.

Changing a driver needs administrator rights: RetroArch starts a copy
of itself with them (one Windows prompt) that makes the change and
exits, and the pad reconnects on its new driver. The prompt is
answered and waited on by a thread of its own, and a task reports the
outcome, so the main thread never waits. An installer can do the same
by running retroarch.exe --winusb-bind-ds3 (or --winusb-restore-ds3).
2026-10-20 04:16:21 +02:00
Codex 347d7fa572 media: keep position across movie backend replacement
Importer refusal and driver reinitialization restarted MP4 playback through a timestamp seek. Composition offsets and rounded frame durations replayed earlier pictures and repositioned audio. Resume at the next display ordinal while preserving clocks and PCM state. Retain catch-up progress when a decoder is busy instead of reopening it on each retry.

Add exact H.264/HEVC picture, PCM and sample-count checks before and after a ten-second seek, plus decoder-capacity fault injection. A reusable native frontend test exercises actual HEVC decode/import, one refused frame, CPU fallback and driver replacement on D3D11/D3D12 against independent FFmpeg RGB references.

Validated with the complete local core workflow (ASan/UBSan, audio disabled, WebM-only, threadless, MinGW/Wine), native Windows/GDB, the full strict-C89 frontend link and compile matrix. Native recovery checks cover 24 pictures and 166174 reference points with maximum channel error 3. Prior code fails picture and PCM continuity.
2026-10-20 04:10:19 +02:00
Codex bd12490d79 win32: preserve math calls in strict C89 builds
MinGW hides float math declarations under strict C89 although its runtime supplies the functions. Force-include scoped declarations in the Win32 C89 lane so viewport rounding and matrix math retain their float return ABI. Keep strict C89 diagnostics; the GNU89 alternative accepts inline and is not used.

Give the three bundled D3DKMDT rotation helpers internal linkage to avoid duplicate definitions across C89 translation units. Add actual runtime and two-unit linkage checks plus seven rejected C99 syntax probes.

Validated through local CI on native Windows/GDB and MinGW/Wine, a fresh complete C89 frontend build, and twelve actual HEVC movie-window frames against independent FFmpeg RGB patches: 796 points, maximum channel error 3, no failures. The previous presentation had 632 failing points.
2026-10-20 03:54:44 +02:00
Codex e3552ea953 vulkan: wait on the captured readback slot
Cached-frame presentation can advance current_frame_index before synchronous readback resumes. Retain the staging slot selected before rendering and wait on its submission fence for SDR and HDR capture.

Exercise the production HDR wait path with a deterministic advancing context in strict C89; the prior source fails the fence identity assertion. Native NVIDIA Vulkan scRGB captures complete at paper white 100 and 200 with Accurate and Super gamut settings. This fixes capture synchronization without changing HDR brightness or gamut policy.
2026-10-20 03:48:39 +02:00
Codex 4d4bfc0fba media: preserve the displayed position after MP4 seeks
Synchronize the builtin player display ordinal with the stream after a timestamp seek. Otherwise a subsequent position save retains the pre-seek frame count, restoring the right audio clock but the wrong video picture.

Expose the next display ordinal through a read-only stream query and cover post-seek H.264/HEVC video and PCM replay with sixteen-second fixtures.
2026-10-20 03:24:25 +02:00
Codex 15cb7c0bae video: recover movie playback after importer refusal
Report direct and worker importer refusals through the readiness query so the builtin player can fall back. Tag queued frames by playback generation to preserve replacement playback after a late failure, and route movie clear through the producer queue. Preserve busy-frame ownership and fail closed on generation exhaustion.

Cover direct and queued ownership, delayed worker results and real threaded-ring recovery, including ThreadSanitizer.
2026-10-20 03:17:30 +02:00
LibretroAdmin c1a8b8f2a4 WinUSB: leave a pad another program has open to it
Windows gives a USB device one driver, and WinUSB lets one program at
a time open it, so a DualShock 3 cannot be read twice: by RetroArch's
WinUSB source and by a joypad driver, or by RetroArch and another
program. What can happen is that another program (ScpToolkit, an SDL
built with libusb) already has the pad open. That is now said once in
the log, as the pad being in use elsewhere, instead of as an open
error at every rescan; the pad is tried again at each rescan and taken
once it is free.
2026-10-20 03:16:57 +02:00
LibretroAdmin 664c4c94b7 Input: add Press Depth to combinations' Response Time
A combination can now wait until its buttons are pushed in far
enough, chosen from 10% to 100%, so that analog triggers and
pressure-sensitive buttons (DualShock 2 and 3) only set off a hotkey,
command or macro when pressed hard. Response Time gains Press Depth
beside Instant and Hold For; a depth and a hold time can be combined,
and Instant clears both.

The depth is read as the core reads the button, through the trigger
deadzone and sensitivity. A button with no pressure reading, or one
held from the keyboard, counts as pushed all the way. It is written
in the configuration as, for example:

   input_combo_4 = "l2+r2 pressed 80% : macro_1"

Only entries with a depth do the extra reads.
2026-10-20 03:06:59 +02:00
LibretroAdmin 00ae5f5eac Input: run WinUSB pads beside the joypad driver
A DualShock 3 bound to WinUSB was only seen with the hid joypad
driver selected. It now works beside whichever joypad driver is
active (xinput, dinput, sdl2...), so it can be used together with
an Xbox pad.

The WinUSB source is started on the first poll of any joypad driver
other than hid. Its pads take device numbers from the shared device
pool, from the top down, and while it has any the joypad driver is
reached through a small stand-in that sends reads for those numbers
to the source and every other number to the driver. With no such
pad the driver is reached directly, so nothing changes per frame for
anyone without one. If the driver's own pad arrives on a number a
WinUSB pad holds, the WinUSB pad moves to another number.

With hid as the joypad driver, WinUSB pads keep their slot numbers
as before.
2026-10-20 03:06:29 +02:00
Codex a8114d645c media: recover when the active movie sink loses readiness
Recheck sink readiness before continuing hardware playback. Reuse the suspend/restart path to release pending leases and seek software-capable streams to the saved position; stop hardware-only AV1 cleanly. Cover runtime readiness loss in the HEVC, VP9 and AV1 player tests.
2026-10-20 02:16:29 +02:00
Codex 75c0d5dd76 metal: exercise coded HDR HEVC activation and FP16 movie pixels
Add eight independently verified BT.2020 PQ/HLG coded Main/Main10 fixtures, preserving the SDR corpus. Check software VUI/plane CRCs and registered adapter capability refusal, activation, withdrawal and software fallback in portable lanes.

Extend the native registered-stream path with four Main10 PQ/HLG sources, following Apple HDR sample-entry constraints. Decode with the shipping HDR capability, consume FP16 movie frames after codec/session/pool retirement, and compare source planes to FFmpeg and rendered linear scRGB to an independent double-precision reference. Main8 color-tagged cases remain portable stress tests rather than an Apple HDR authoring claim.

Validation: complete GCC/Clang portable Metal jobs, Windows C89/GDB, 94 workflow selftests, scoped ARC importer syntax, eight FFmpeg plane oracles and 1120 HDR reference components/absolute whites. A CPU float32/half error-budget simulation passes all four Main10 sources; this is not GPU evidence. Actual Apple coded-HDR and broader player acceptance remain pending.
2026-10-20 02:06:57 +02:00
Codex 4381dc33a6 metal: convert HDR movie planes and present linear scRGB
Convert explicit BT.2020 nonconstant-luminance PQ/HLG NV12/P010 into FP16 linear scRGB on the GPU. Use FP16 movie history and a dedicated HDR10/scRGB presentation fragment, preserving absolute luminance and the existing core HDR uniform ABI. The retained backend capability gates HDR activation; unsupported renderer/color combinations retain software fallback. Generic planar texture imports use the same HDR conversion.

Restore history, staging texture, dimensions and movie state if destination allocation or encoder setup refuses an import. Persistent history scratch avoids a history-sized stack allocation. CPU/deferred frames clear movie color state and reinitialize history when leaving HDR.

Validation: complete GCC/Clang portable Metal jobs, final 128 movie and 96 planar mock cases, Windows C89/GDB, 94 workflow selftests, 44-driver thread-state check, complete compile matrix with ARC/MRC Metal rows, final focused Metal rows and scoped native-presentation Objective-C syntax. Native PQ/HLG conversion/history and stock-MSL presentation pixel fixtures are added; actual Apple GPU and full player acceptance remain pending.
2026-10-20 01:54:32 +02:00
LibretroAdmin 032bad5dcf input: one pool of device numbers for the joypad driver and sources beside it
A port reads its controller by device number, and today that number is
the joypad driver's own slot. A pad no joypad driver sees - a
DualShock 3 bound to WinUSB - needs a number from the same pool, so
that it can sit beside the driver's pads without either taking the
other's.

input_device_pool.h keeps that pool. The driver's pads keep their slots
as their numbers, so nothing the driver reads changes. A source beside
it claims free numbers from the top down, and a pad that comes back is
given its number again if it is free. Where the driver's pad arrives on
a number a pad beside it holds, the driver's pad keeps it and the other
is named to move. The frontend marks the driver's numbers as it learns
of its pads, and clears them when the driver reports its pads afresh.
Nothing claims a number beside the driver yet; the WinUSB source will.

samples/input/device_pool runs the pool through mixed arrivals and
departures from the driver and two sources beside it.
2026-10-20 01:52:06 +02:00
LibretroAdmin 351e799a3e connect_ps3: read the DualShock 3's sticks the right way up
Both sticks' vertical axes were turned round: a stick held half up
read as half down, and one held fully down read as fully up. Only a
stick held fully up came out right, by its value overflowing. The
pad's 0 is already left and up, libretro's negative, so no axis is
turned round now, and each is scaled over the whole range.

A report that is not the whole input report (ID 0x01, 49 bytes) no
longer becomes the pad's state.
2026-10-20 01:52:06 +02:00
Codex f40d035223 metal: test decoded HEVC frames through the movie importer
Consume each native registered-decoder lease through the shipping FrameView movie conversion and history methods after codec/session/pool teardown. Preserve the source-plane FFmpeg CRC checks by encoding fixture-only readback before the importer consumes the single lease. Check all rendered RGBA pixels with an independent double-precision BT.709 reference and reset to a same-size CPU front between streams.

Verify the reference against 1120 fixed golden color components in strict C89 across portable lanes. Complete GCC/Clang Metal jobs, Windows GDB and 94 workflow selftests pass; the new importer scope also passes an Objective-C syntax probe against local SDK stand-ins. Actual Apple SDK/GPU and full window/player acceptance remain pending.
2026-10-20 01:33:25 +02:00
Codex 1e3507cbc9 metal: register the device-bound HEVC backend during startup
Publish the retained Metal device only when the SDR conversion pipeline and shared-event OS gate are available. Withdraw by owner at teardown and retire decoder sessions on a process-lifetime serial dispatch queue. Capability queries remain atomic; hardware settings are checked during decoder setup.

Link CoreMedia and VideoToolbox in the Metal build routes. Annotate HEVC setup availability so older deployment targets can include the guarded startup path. Exercise the shipping bootstrap in the native registered-stream fixture and add a macOS 10.13 compile probe.

Validation: complete GCC/Clang portable Metal jobs, final selected HEVC sanitizer lanes, Windows C89/GDB, 94-workflow selftest, 44-driver thread-state check and complete compile matrix including feature-enabled Metal. Apple SDK/runtime and full player acceptance remain pending.
2026-10-20 01:28:20 +02:00
Codex c827f9082d video: retain HEVC device snapshots across backend replacement
Protect device publication, setup acquisition and withdrawal with a setup-only registry lock. Retain each snapshot while protected; capability ownership queries remain lock-free and decode/output/retirement never acquire this lock.

Add compare-and-withdraw for HEVC offers and use it in D3D11/Vulkan teardown so an old driver cannot remove a replacement backend. Exercise stale owners, foreign offers and a held snapshot spanning 1000 replacements with concurrent acquisitions; native registered streams now use the registry too.

Fix the Metal compile-matrix regression exposed by broader validation: include the existing dispatch stubs and declare the queue priority and CoreVideo cache factory used by shipping code. Preserve the strict Metal row and native acceptance gates.
2026-10-20 01:17:02 +02:00
Codex 1a88efbd78 metal: validate movie history with shipping texture helpers
Use the actual FrameView history, front-format and allocation methods in the shared ownership fixture. Check distinct slots, rotation, resize and same-size CPU format transitions. Bound movie-import temporaries with its own autorelease scope after the stronger fixture exposed delayed texture releases.

Add native serial/batch movie conversion pixels and history checks to the existing independent reference test. Run native Metal acceptance as a separate Apple workflow step even after HEVC failure. This remains a windowless fixture; frontend device lifecycle and capability activation are still separate work.
2026-10-20 01:02:21 +02:00
Codex e961e47404 metal: import movie leases into normal frame storage
Implement the movie-frame hook using the existing SDR GPU converter and FrameView front/history textures. Use RGBA8 storage for imported movies and check front pixel format across same-size transitions back to CPU uploads. Preserve imported GPU frames on NULL refreshes and avoid treating the hardware sentinel as a CPU pointer.

Extract the actual import method into 48 portable conversion/refusal/clear/retirement cases. Clearing a movie must not suppress pending software conversion. Keep import capability advertisement disabled until backend lifecycle and native player acceptance are integrated.
2026-10-20 00:53:57 +02:00
Codex f9b650c32a videotoolbox: expose an owned-device HEVC backend offer
Connect codec registration to stable device snapshots, the bounded Metal pool and deferred decoder owner. Refuse disabled or unavailable hardware and optionally gate activation by the existing SDR importer contract. Release acquired devices and failed pools on every setup path.

Test real codec activation, registry withdrawal, device/pool refusal and software fallback in all four Main/Main10 range cases. Drive the twelve-stream native fixture through registered codec decode/drain and verify exported pixels in presentation order after teardown. Frontend Metal startup and movie import are not enabled by this change.
2026-10-20 00:46:58 +02:00
Codex 0491587e3e metal: share device-bound CoreVideo export setup
Create the cache, completed ready event, listener and private completion events in the shipping bridge. Retain successful setup through the pool and clean partial allocation under ARC and manual reference counting. Use local protocols and runtime capability checks for shared events.

Route native adapter acceptance through this factory. Exercise its actual body in the ownership mock with event/cache/listener failures, unsupported devices, invalid capacity and maximum slot setup, requiring device references to return to baseline.
2026-10-20 00:42:03 +02:00
Codex 43af306b44 videotoolbox: validate native adapter exports after teardown
Drive twelve real IDR/I-P/B streams through the shipping decode/output adapter. Hold 68 exported leases until the source, adapter, session and export pool are destroyed, then consume each through the actual Metal encoder and compare all planes with independent FFmpeg CRCs.

Compile the native C control flow in the portable SDK-limit lane, keeping host limits for this full test body and retaining synthetic Apple-limit checks on the shipping headers. This fixture adds no frontend activation and requires actual Apple execution for acceptance.
2026-10-20 00:38:34 +02:00
Codex 1d19561270 videotoolbox: adapt owned sessions to HEVC hardware operations
Implement decode/output/close using the existing callback slots, deferred owner and bounded CoreVideo-to-Metal pool. Preserve decoded images across output pressure, reject synchronous frame drops, and let exported leases outlive adapter teardown.

Exercise eight portable Main/Main10 range cases for admission failure, pending retries, export busy/error, metadata, dropped frames and close with accepted work outstanding. Compile the adapter in the C89 SDK probe. Remove the native B-batch test flush: temporal processing is disabled, so final outputs must complete without caller-side draining.
2026-10-20 00:34:13 +02:00
Codex 4a96fde7e3 metal: bound CoreVideo exports with independent completion slots
Retain setup resources in a single-producer pool and export decoded NV12/P010 buffers through the existing zero-copy plane-view bridge. Reserve an event per outstanding image and reuse it only after its own prior completion; return busy immediately when all slots are occupied. Failed exports consume no value, and leases remain valid after pool teardown.

Run the same pressure, out-of-order completion, monotonic reuse and lifetime fixture in the Objective-C mock and native Metal/VideoToolbox harnesses. Keep frontend decoder registration separate until native acceptance is evidenced.
2026-10-20 00:29:56 +02:00
Codex 8aa2b8121f videotoolbox: drain accepted frames on the retirement executor
Finish asynchronous and delayed framework callbacks before releasing their slot storage. Keep this blocking operation on the deferred executor; caller retirement still only closes admission and enqueues its owner.

Exercise immediate retirement after all eight access units of each Main/Main10 limited/full B sequence. Preserve result storage through an observer reference and verify timing, native pixels and Metal export after session destruction. Portable mocks keep all 32 tickets pending until the worker drains them.
2026-10-20 00:26:24 +02:00
Codex fa3eae8b4b videotoolbox: hand owned sessions to a retirement executor
Close admission and clear the owner handle before enqueueing teardown, using storage allocated during setup. Retire the session and unread slot results off the caller thread while exported images retain their own lifetime. Exercise delayed callbacks, stopped tickets, failure cleanup and native decoded images across retirement.
2026-10-20 00:21:14 +02:00
Codex 6850f230fa videotoolbox: use pixel-validated streams for native session checks
Keep the parameter-only fixtures for parser, ownership and malformed-input checks. Require native session success for the real Main/Main10 streams with independent pixel references, and exercise those setup checks in portable builds too.
2026-10-20 00:21:14 +02:00
Codex 44fb7d19b9 mp4: retain HEVC parameter NALs before hardware startup
Native HEVC backends need the original VPS/SPS/PPS bytes, which a software-opened stream did not retain. Recreate HEVC through the existing rewind path when hardware is requested and retain its configuration before parsing. Verify backend parameter queries on activation and rewind, plus software decoding after hardware refusal.
2026-10-19 23:56:14 +02:00
Codex 578d4b4b90 ci: run native HEVC acceptance independently of macOS builds
Unrelated master pushes replaced pending macOS runs before the HEVC job could start. Move the short native decode/Metal check into its own source-filtered workflow without the shared build concurrency group. Retain the command, timeout and unavailable-result warning.
2026-10-19 23:49:54 +02:00
Codex 875a828c42 videotoolbox: validate real HEVC streams through hardware owner state
Exercise display ordering, reference retention, immutable backpressure retries and stale-commit rejection with the I/P and B-picture corpora. Cover normal draining and forced output pressure without requiring an Apple decoder.
2026-10-19 23:49:37 +02:00
LibretroAdmin 077f1df9b8 winusb: raise the reader's priority; log its report counts
A pad's reports are waited for on a thread of its own. The main, video
and audio threads all raise their priority, and the reader did not: a
report left waiting behind them is a press or release the frontend
sees late. It now raises its own as they do.

When a pad goes, the debug log says how many reports it sent, how many
the poll took, how many were replaced before it did, and the longest
time between two - so a pad that stops reporting for a while can be
told from one whose reports arrive late.
2026-10-19 23:46:43 +02:00
LibretroAdmin 7b239db272 runloop: set the frame limit from a core's new frame rate
The limit is the frame rate times the fast-forward ratio, and while the
core runs nothing else recomputes it: a SET_SYSTEM_AV_INFO with a new
frame rate sets it from that rate.
2026-10-19 23:43:46 +02:00
Codex 4fcd3e8645 videotoolbox: diagnose native sessions after real-stream acceptance
Attempt the real coded Main/Main10 corpus before parameter-only hardware-session probes. Keep every setup assertion and report the exact status, dimensions, depth and range on rejection so native CI distinguishes the failed fixture from a compiler or decoded-frame failure.
2026-10-19 23:37:58 +02:00
LibretroAdmin 21622801ec connect_ps3: start a DualShock 3 on USB without Bluetooth's request
The start set feature report 0xF4 first and gave up when the pad
refused it. 0xF4 is the request that starts the pad over Bluetooth; a
DualShock 3 on USB stalls it, and starts when 0xF2 is read. On Windows
the pad bound to WinUSB was found and never started ("Failed to send
activation packet"), and the same would happen through hidraw.

0xF2 is now read first and 0xF4 sent after; the pad counts as started
when either is answered. A refused 0xF5 no longer fails the start.

The WinUSB and hidraw tests' stand-ins refuse 0xF4, as a pad on USB
does.
2026-10-19 23:37:38 +02:00
LibretroAdmin fc1f6e056a audio: keep the device open across a content switch
A content load or close sets the audio device aside stopped instead of
closing it, and the next init takes it back when it would open the same
one: driver, device, output rate, latency, threading and the thread's
settings. Everything else - ring, arenas, resamplers, mixer - is built
afresh for the new core as before. A device the next init would open
differently, or that nobody takes back, is freed.

The threaded wrapper is kept parked, so its thread reads nothing that
is rebuilt meanwhile. RETROARCH_AUDIO_KEEP=0 in the environment closes
and reopens the device as before.
2026-10-19 23:21:05 +02:00
Codex 070f431f81 videotoolbox: scope C89 inline compatibility to Apple SDK headers
CoreMedia declares static inline functions even in strict C89 builds. Use the compiler spelling while including Apple frameworks and restore the caller's macro state afterward. Exercise the shipping helper with an SDK-style declaration and reject alias leakage in the portable compiler probe.
2026-10-19 23:16:06 +02:00
Codex d75509c628 videotoolbox: exercise decoded images through Metal leases
Pass retained native HEVC output through the shipping CV texture exporter after decoder teardown. Check GPU plane CRCs and delayed abandonment after releasing the original pixel buffer and texture cache. Keep waits and readbacks in the acceptance fixture.
2026-10-19 23:11:51 +02:00
Codex 79abce8885 compat: avoid SDK long-long constants in native C89 paths
SDK limit macros may expand to C99 LL/ULL literals. Use typed unsigned maxima and small integer timeout factors without changing arithmetic. Add strict compile probes for the decoder, owned-sample headers and portable pthread timeout branch while retaining Werror.
2026-10-19 22:58:36 +02:00
Codex b393d8b6e9 videotoolbox: validate reordered HEVC B-picture streams
Pin decode and presentation clocks plus FFmpeg pixel references for four Main/Main10 B streams. Check past/future references, software display-order drain and owned sample PTS/DTS association. Preserve existing I/P packet bytes and references.
2026-10-19 22:46:48 +02:00
LibretroAdmin c1f5c0e17d audio: keep the device open across AV info that leaves video alone
A SET_SYSTEM_AV_INFO that keeps video - a new frame rate, base size or
sample rate - goes to the running audio driver in place: the rate is
recomputed with the frame rate's timing, and the threaded pipeline's
ring and slice, sized in video frames, follow a frame rate change
under the consumer's park. A full reinit remains the fallback for what
the in-place path cannot carry.

A driver reinit that included audio puts back what the audio reinit
restores after its teardown directly, instead of a second audio
reinit for the menu sounds; a reinit that did not run asks for none.
2026-10-19 22:40:57 +02:00
Codex b994b989ef videotoolbox: validate dependent HEVC picture sequences
Add four Main/Main10 I/P streams with range-preserving FFmpeg pixel references. Require each P picture to use its preceding reference, compare software reconstruction and drain, and submit distinct packets through the owned batch consumer. Native acceptance checks each output against its own pixel reference after decoder teardown.
2026-10-19 22:37:08 +02:00
Codex ef549b2a13 videotoolbox: exercise multiple outstanding codec samples
Submit eight timed IDR samples per session before consuming results. Check busy refusal, reverse delayed mock callbacks and source ownership, then retain all images across decoder teardown. Native acceptance additionally checks every output pixel plane and rational timestamp.
2026-10-19 22:28:29 +02:00
Codex dbcae1f6f0 videotoolbox: preserve rational frame timing through decode
Copy optional sample timing into owned Core Media samples and publish callback presentation time and duration with each image. Preserve the existing untimed API and exact CMTime fields across reordered completion and slot reuse. Exercise borrowed timing replacement and rational timing in the real codec fixture.
2026-10-19 22:19:17 +02:00
Codex 0afe742601 videotoolbox: close slot admission before retirement
Let the owner reject further reservations without discarding accepted callbacks or completed images. Keep close owner-side and nonblocking, and exercise completion, drain and owner-release races after close. Session destruction and playback activation remain separate integration work.
2026-10-19 22:08:41 +02:00
Codex 579ddfb9db videotoolbox: add real HEVC decode acceptance fixtures
Check four spatial Main/Main10 full/limited-range access units against range-preserving FFmpeg references. Exercise owned sample submission and decoded-image lifetime beyond source and decoder release. Native pixel mapping is test-only; hardware CI remains an acceptance gate before production integration.
2026-10-19 22:01:13 +02:00
Codex fdcfd8e4c6 videotoolbox: publish callback results through owned slots
Reserve slots before submission, retain successful CVPixelBuffers in callbacks and transfer their references through a release/acquire handoff. Each accepted ticket keeps its context alive across owner unload; rejected submissions retire without a callback.

Expose decode information flags and require externally established callback quiescence before retiring undelivered tickets. Session shutdown and playback registration remain gated.

Validate saturation, reverse completion, image transfer, errors, drops, synchronous and delayed submission, concurrent publication and owner-release races. Cover GCC/Clang C89, ASan/UBSan, TSan, threadless/volatile refusal, native Windows GDB and a full threadless C89 frontend link.
2026-10-19 21:45:30 +02:00
Codex 2326cd3736 videotoolbox: own HEVC access units through submission
Build one Core Media sample from validated VCL NALs with four-byte lengths and independently owned compressed storage. Refuse malformed headers, inconsistent byte counts and unrepresentable lengths before allocation.

Submit with asynchronous decompression enabled while preserving synchronous callback behavior. Cover delayed packet reuse, immediate completion, failed submissions and partial allocation cleanup. Callback context and decoder-slot ownership remain with the caller; decoder registration stays gated.
2026-10-19 21:31:43 +02:00
LibretroAdmin 4763f4009b audio: free an instance already up before bringing audio up again
A core that sends SET_SYSTEM_AV_INFO from retro_load_game has audio
brought up by that reinit and again by drivers_init(). The first
instance is torn down as driver_uninit() would; on the threaded
pipeline its thread otherwise keeps consuming the ring beside the
new one's, the two splitting the core's audio between them.

Diagnosed by pstef.
2026-10-19 21:23:00 +02:00
Codex 0ab6f516ba hevc: retain parameter NALs for VideoToolbox session setup
Opt in to owned VPS/SPS/PPS NALs and resolve the selected parameter chain without reconstructing escaped syntax. Cache refusal preserves software parsing and disables the affected hardware chain.

Consume those NALs in hardware-only VideoToolbox session setup with Metal-compatible NV12/P010 buffers. Cover ownership, replacement, setup failures and Main/Main10 range selection in portable and native fixtures. Decoder submission and renderer capability publication remain gated.

Validation: strict C89 GCC/Clang, ASan/UBSan HEVC oracles in all three threading modes, Metal mocks, compile matrix, workflow selftest and native Windows setup under GDB.
2026-10-19 21:21:57 +02:00
LibretroAdmin 0f90f691d0 audio: take a core's sample rate change without reopening the driver
A SET_SYSTEM_AV_INFO that changes only the sample rate swaps the
resamplers and ratio in place, growing the threaded pipeline's ring
under the consumer's park when the new rate needs more room. The
driver and device stay open. Configurations the swap cannot carry
(time stretch, DSP, callback and inline transports) take the full
reinit.

retro_spsc_grow() enlarges a quiesced ring, keeping its positions.

wdmks_loop runs its modelled cases on a clock that moves only as the
model waits; wasapi_exclusive_pacing re-runs a scenario that misses
its budget and fails on three misses in a row.
2026-10-19 20:54:23 +02:00
libretroadmin 091878adf0 d3d11/d3d12: parenthesize the HDR back buffer condition
Groups the && inside the || as C already binds it, so -Wparentheses
stays quiet; behaviour is unchanged.
2026-10-19 20:38:11 +02:00
libretroadmin 7234101942 vulkan: Driver Pacing through NVAPI when presenting through DXGI
Vulkan presenting through DXGI never calls vkQueuePresentKHR: frames go
to a D3D12 device's swap chain, which VK_NV_low_latency2 cannot pace,
so Driver Pacing stayed off ("no native swapchain") on exactly the
setups that present that way - HDR, and NVIDIA's frame generation.

There the mode now goes through NVAPI's Reflex entry points on the D3D12
device that presents, as the Direct3D drivers' does: the presenter sets
the mode and marks the frame and its presents on that device, and the
frame loop's release is NvAPI_D3D_Sleep on it, shown to the frame loop
only while the mode is in effect there. The device is referenced while
used, so a sleep never outlives it. A failed sleep falls back as the
native path's does, and leaving DXGI takes the mode off the device.
Without NVAPI (Anti-Lag) the status still says the present is in the
way.

The NVAPI backend moves out of d3d_common.c into d3d_driver_pacing.c,
built with Vulkan on Windows as well as with the Direct3D drivers.
samples/gfx/vulkan_driver_pacing builds under mingw-w64 and runs under
Wine against a stand-in nvapi64.dll, driving the real vulkan_common.c
through a DXGI presenter: mode, boost, interval, markers, sleep, a
failed sleep, threaded hand-off and the way back to the native path.
CI runs it there beside the Linux runs.
2026-10-19 20:35:18 +02:00
libretroadmin 9a4f3815bd vulkan: say when Driver Pacing is off for a core's own device
A hardware core that makes its Vulkan device through the first
negotiation interface (create_device) gets only RetroArch's required
extensions, and one that bypasses create_device2's wrapper gets none
of them: VK_NV_low_latency2 and VK_AMD_anti_lag are optional, so such a
device has neither, whatever the GPU offers. The status then read "off:
not offered by the GPU driver", which sent people looking at their
drivers.

Such a device is now told apart (its extensions were never seen by
the wrapper) and the status reads "off: the core made the device
without it". samples/gfx/vulkan_driver_pacing checks the reason.
2026-10-19 20:35:14 +02:00
Codex 2d52463117 metal: cover pending planar imports and texture replacement
Keep the serial CoreVideo pixel cases and add a batch of eight leased imports before GPU execution. Check five dimension replacements, two reuses, stable handles and independent readback pixels after delayed producer readiness.

Release the public texture handle and setup-owned pipeline before batch completion so native acceptance exercises command-buffer resource ownership. Production code and decoder capabilities are unchanged.
2026-10-19 20:31:18 +02:00
Codex 7fb1b26a6f metal: reconcile CoreVideo frame color attachments
Recover absent source color fields from supported image-buffer tags and reject explicit conflicts before constructing plane views. Preserve supplied mastering and content-light metadata without inventing defaults for missing attachments.

Cover attachment consistency and refusal in the mock, and feed real CoreVideo tags into native plane and importer pixel fixtures. Decoder selection remains gated.
2026-10-19 20:27:07 +02:00
Codex 9c8931ba1c metal: import leased SDR planes into persistent textures
Create the SDR conversion pipeline during context setup and route explicit NV12/P010 leases through it. Preserve existing handles and images on refusal; publish replacements only after encoding accepts ownership.

Cover importer rollback and retirement in the mock, and exercise persistent conversion through the native CoreVideo pixel fixture. Keep decoder capability advertisement gated.
2026-10-19 20:14:27 +02:00
LibretroAdmin 001fe040f5 android: size the window to the cutout safe area again
Revert ed3187ec8e. On Android 15 the window manager rejects a main
window that is not translucent asking to fit the display cutout insets
("Illegal attributes ... trying to fit insets"), so the activity never
resumed. The window is placed in the safe area by hand once more when
drawing over the notch is off.
2026-10-19 20:00:50 +02:00
Codex dd77e1d322 metal: convert leased NV12/P010 SDR frames on the GPU
Convert explicit BT.709 planar input to RGBA8 sRGB with exact eight- and ten-bit levels, crop offsets and nearest chroma reconstruction. Build the owned compute pipeline outside frame submission and preserve completion/discard ownership.

Refuse HDR, unknown color metadata and incompatible resources. Keep planar importer integration and decoder format advertisement gated. Add independent native pixel references and portable endpoint, encoding and retirement checks.
2026-10-19 19:55:44 +02:00
Codex 09fcc9536b metal: accept CoreVideo upper-left plane coordinates
CVMetalTextureIsFlipped reports true for an upper-left texture origin. Require that orientation for frame leases and model it correctly in the mock; reject lower-left views. Keep the native spatial pixel assertions unchanged.
2026-10-19 19:25:00 +02:00
Codex 05eeb6b105 metal: retain CoreVideo plane views in frame leases
Create NV12/P010 Metal views through a caller-owned texture cache. Keep the pixel buffer and both CoreVideo wrappers until completion or asynchronous discard. Infer range from the pixel format and refuse conflicting metadata, flipped views and device mismatches.

Cover partial failures and GPU-error retirement in the mock fixture, plus real IOSurface plane pixels and discard on the native runner. Decoder selection remains gated pending planar conversion and producer integration.
2026-10-19 19:19:33 +02:00
Codex c0b28bd3d7 metal: exercise packed import replacement on the native GPU
Run the shipping importer and private-texture allocator against real Metal resources. Check stable public handles, image reuse and resize or format replacement while queued reads retain both images. Compare cropped BGRA, RGBA and above-white FP16 pixels after releasing the public handle before execution.
2026-10-19 19:04:17 +02:00
Codex b17fc323ee slang: detect feedback from compiled shader resources
Use active SPIR-V samplers and buffer members instead of searching raw source text. Comments, disabled branches and unused declarations no longer force a final target; macro-expanded feedback still preserves it.

Keep deferred compilation incremental. Reserve an optional copy slot, accumulate usage as passes compile, and prune the unbuilt slot before building a direct last pass. Resolve its pragma name early so preceding deferred passes can use its feedback. Apply the decision to Vulkan and glcore without changing shared reflection behavior for other backends.

Exercise actual glslang and SPIRV-Cross under sanitizers and mingw-w64, including token pasting, buffer-member activity, vertex-stage usage and malformed-module refusal.
2026-10-19 18:36:17 +02:00
libretroadmin 7a3d5947ce vulkan: take Driver Pacing off with a sleep mode, not NULL
vkSetLatencySleepModeNV's pSleepModeInfo is not optional, and taking
the mode off passed NULL for it. NVIDIA's driver reads the structure
whatever the mode and faulted on it (a read at 0x18, the minimum
interval), crashing RetroArch wherever Driver Pacing stands aside -
the menu opened over a running core, pause, fast-forward - and when
the mode is dropped after a failed sleep.

The mode is now taken off with the structure filled in and
lowLatencyMode, the boost and the interval cleared.

The fake driver in samples/gfx/vulkan_driver_pacing aborts on a NULL
pSleepModeInfo as the real one does, and the checks that expected the
NULL expect the cleared mode; it aborts without the change.
2026-10-19 18:10:30 +02:00
libretroadmin 08d96aba04 video: Driver Pacing releases the frames Display Pacing would hold
Threaded video's Display Pacing and Driver Pacing each decide when the
core's next frame starts, so with both on Driver Pacing stood down -
its status read "off: menu, pause, fast-forward or unfocused" - and
the presenter's hold, sleeping to its own estimate of core and render
time, kept the release.

The driver's sleep is now the hold's release: with both on and Driver
Pacing in effect, the runloop sleeps on the driver as it does without
Display Pacing, and the hold in the wrapper's frame handover stands
aside for a frame the driver released (one whose frame info carries a
release identity) instead of sleeping a second time. The cadence the
hold kept - the content's period, which on a display faster than the
content is not the display's - goes to the driver as the minimum
interval it is to keep. A release the driver could not give leaves the
frame to the hold as before, and where Driver Pacing stands aside (the
menu, pause, fast-forward) the hold holds. Scanline Sync still keeps
Driver Pacing out.

Off by default with Driver Pacing; with Display Pacing alone nothing
changes. The two settings' descriptions say how they combine.

The pacing table's display-pacing row now has the driver beside the
hold, and a new row keeps the menu to the hold alone. The threaded
harness lane checks, under Display Pacing, that every frame is
released by the driver, that the hold stood aside for them, that the
content's period reached the driver with the frame, that a failed
release leaves the hold to hold, and that turning Display Pacing off
leaves the driver alone with its own interval.
2026-10-19 18:03:57 +02:00
libretroadmin 7c8f03c719 d3d12: two frames in flight under Driver Pacing
Every D3D12 frame began by waiting for the last frame's command list to
finish on the GPU, since what that list read - its constants, sprites,
descriptor tables, upload buffers - the next frame writes again in
place. With Driver Pacing on, NVAPI's sleep already holds the frame
back until the GPU is close to wanting it; the wait then took the
remaining overlap away and moved the stall rather than removing it.

A frame Driver Pacing released now waits only for the frame before the
last. What a frame's lists read and the CPU writes again is kept a slot
apiece (D3D12_FRAMES, two), and a frame takes the slot after the
last's:

 - a command allocator a slot, reset once the slot's lists have run;
   a list after the frame's own (a black frame, a repeat) follows it
   on the queue without waiting;
 - the driver's constant buffers, the shader passes' and the HDR ones,
   a 256-byte aligned copy a slot; the constants draws bind without
   writing are carried to a slot when it is taken;
 - half the sprite ring, the views' constants and the overlay's
   vertices a slot;
 - a second texture table for every shader pass, the chain's and the
   views' chains', each with its own cache of the views it holds;
 - a texture's upload buffer, while a frame still copies from it, gives
   way to its second (the one lending already makes) instead of being
   written under the copy; the font's atlas, staged in place, is staged
   whole into the second when it turns;
 - a texture let go while a frame in flight may draw from it goes
   behind the frame being recorded (d3d12_retire), its views with it,
   and a frame texture's view that changes takes a new slot, the old
   one retired the same way. Nothing waits for the GPU but where the
   memory to keep these runs out.

Without Driver Pacing (or with it off for the frame: the menu, pause,
fast-forward) a frame waits for the last list as before, nothing is
in flight past a frame's top, and every path above takes the branch
it took before; the slots alternate all the same. The exception is a
texture let go between frames (an overlay loaded), which now goes
behind the last frame instead of being released under it. Planar
textures still drop an update that finds their planes in use.

samples/gfx/d3d12_frame_ring runs the slot, recording and upload turn
code against a fake queue that finishes only what a wait asks for: one
frame in flight waits for the last list every frame and resets every
allocator as before; two wait only for the frame before the last, a
repeat does not wait, no allocator is reset under a list still in
flight, and a texture let go under one is retired, not released. The descriptor cache and pass table tests cover the second
tables. CI runs it plain with sanitizers and through mingw and Wine.
2026-10-19 18:03:53 +02:00
libretroadmin 7c66eb00b9 d3d12: shader passes keep the descriptor tables made for them
d3d12_init_descriptors() makes every pass its own tables,
SLANG_NUM_BINDINGS slots side by side in the SRV heap and in the sampler
heap, pass after pass. d3d12_chain_free() clears the passes, handles and
all, so both ways a preset is installed (the deferred finalize and
d3d12_gfx_set_shader) point each pass at its tables again - at
pass * SLANG_NUM_SEMANTICS slots from the heap's start: 35 apart for
tables 16 wide. From the second pass on the tables lay across another
pass's slots. From pass 29 (counting from 0) they lay past all of them,
on the SRVs of textures made later and on the stock samplers, and from
pass 30 beyond the end of the sampler heap.

The installs now point each pass at pass * SLANG_NUM_BINDINGS, where
init put its tables.

samples/gfx/d3d12_pass_tables runs the production init, then each
install block over cleared passes, against a fake heap that records
which pass each slot was made for, and checks every pass's bindings lie
on its own; it fails at pass 1 without the change. CI runs it plain
with sanitizers and through mingw and Wine.
2026-10-19 18:03:49 +02:00
libretroadmin c5b072d6c9 vulkan: Driver Pacing through AMD Anti-Lag
Devices with VK_AMD_anti_lag and not VK_NV_low_latency2 get Driver
Pacing through vkAntiLagUpdateAMD. The frame loop's INPUT update is
the hold, inside the driver, naming the identity the released frame
will carry; the presenter's PRESENT update names that frame once, before
its own present, and the presents after it (repeats, dark frames,
sub-frames) send nothing. The frame limit is the timer's period, set
from the frame that brings it. The mode is the device's, so it crosses
threads as one atomic and comes off with a single update; Boost has no
counterpart and reads as On.

The wait poke now also takes the identity the frame about to be
released will carry, which Anti-Lag names at its input and the other
backends ignore. The types are the registry's, declared locally where
the tree's Vulkan headers predate the extension, and the feature is
enabled on RetroArch's device and on a core's through create_device2.
2026-10-19 18:03:45 +02:00
Codex 41a3e57abb tests: repair Objective-C linking and Vulkan pacing fixtures
Find the GCC Objective-C runtime alongside the headers used by the Metal mock, independently of Clang default toolchain selection. Supply the direct-context wrapper query in the Vulkan validation sample. Drain outstanding frames before changing the pacing harness driver pointer so widget rendering cannot race its replacement.
2026-10-19 09:46:13 +02:00
Codex 054601aa63 tests: keep Vulkan pacing regression independent of Xlib
The fake-entry-point pacing test does not create a window or a surface. Do not enable the Xlib Vulkan platform declarations, which require X11 development headers absent from this job.
2026-10-19 07:40:27 +02:00
Codex bd77de24e1 metal: accept device-independent shared events
Shared events may report a nil device because they synchronize across devices. Accept that valid form while retaining the checks for foreign device-bound events and texture ownership. Exercise both event forms in the lease mock, including rejection of foreign ready and done events.
2026-10-19 07:04:49 +02:00
LibretroAdmin b8e8f1ae92 audio: add AAudio driver for Android 8.0 and up
AAudio is resolved from libaaudio.so at runtime, so the build needs no
API 26 headers, the library gains no dependency on it, and the binary
still runs on older releases. Below API 26 the frontend takes the
driver out of the list at startup, before the configuration or the
menu reads it; OpenSL stays the default.

The stream is a low-latency callback stream at the device's own rate,
fed from a mirrored retro_spsc ring the writer parks on through the
eventcount. The device stage is two bursts and the ring holds the rest
of the latency setting. The driver provides the lend pair,
wait_writable, frames consumed on the device's clock and underruns.
Layouts wider than stereo open as a channel mask on API 32 and as a
count with Android's canonical positions before it. Outputs can be
picked from AudioManager's list. A disconnect frees the writer and
asks the frontend to reopen, and a late report from a closed stream is
dropped.

A wider layout whose output takes fewer PCM channels than asked, but
lists AC-3 among its encodings, goes as AC-3 over IEC 61937 (API 34),
encoded with rac3 as WASAPI does. Underruns are filled with pause
bursts, and a burst is written whole or not at all. Android.mk gains
HAVE_RAC3 for it.

samples/audio/aaudio runs the driver against a scripted libaaudio
behind dylib_load, on Linux and with mingw-w64.
2026-10-19 07:03:11 +02:00
libretroadmin 23aed644e1 video: Driver Pacing under threaded video, and through BFI, sub-frames and swap intervals
The presenter now owns the backend's frame side and the frame loop
only waits: each content frame carries its identity and the minimum
present interval in video_frame_info_t, the presenter marks, presents
and issues the one sleep after the frame's own present, and the frame
loop waits on the timeline semaphore for the identity it last handed
over before it samples the next frame's input. That is the same shape
on the direct path and under the threaded wrapper, which forwards the
three pokes to the driver unchanged, so Driver Pacing is no longer
turned off by Threaded Video; only Threaded Video Display Pacing, the
presenter's own hold, keeps the release to itself.

Any present after a frame's own - a repeat, a dark frame, a shader
sub-frame, a swap interval the driver emulates by presenting again -
is marked out of band on the same identity, which is what the
extension's attribution rules provide for, so Black Frame Insertion,
Shader Sub-frames and swap intervals above one no longer keep the mode
off; Scanline Sync, which paces the present itself, still does. The
Direct3D backends mark their presents the same way through a helper
around every Present, and take the interval from the frame on the
presenter's thread.

What crosses between the two threads in the Vulkan backend is
lock-free: the mode request and the presenter's answer are atomics
(a request the presenter has not seen reads as pending, never as in
effect); the swapchain the frame loop's input and simulation markers
may name is published under a generation, rewritten only while that
thread is seen outside a marker call, and a replaced swapchain is kept
until it is, so a marker never names a destroyed one. A wait the
driver does not satisfy four times running takes the mode off and
reports failure; asking again tries anew.

The fake-backend sample covers the sleep-after-present cadence, the
out-of-band markers, the cross-thread request and the kept swapchain;
the harness lane adds the threaded path through the wrapper on the
null driver and on Vulkan, and the pacing table pins the wrapper row.
2026-10-19 06:54:41 +02:00
Codex e5723d612d tests: fix strict Clang and GLES harness regressions
Preserve the native bool type in the strict C89 geometry probe through an explicitly scoped Clang extension and exercise that compiler on Linux. Supply video state for deferred auto-state tests now reached by pacing input markers. Restrict replaceable fence-symbol injection to desktop GL and use framebuffer readback in GLES, where glGetTexImage does not exist. Keep GLES pixel and lending assertions enabled.
2026-10-19 06:33:32 +02:00
Codex bc99c3b23d metal: replace imported images without losing the last frame
Prepare size and packed-format replacements before encoding, then swap the image and sampler only after acceptance. Keep the public Texture handle stable for wrapper bookkeeping and preserve the old image on allocation or encoder refusal. Encoded GPU readers retain the previous image independently.
2026-10-19 06:25:28 +02:00
Codex df7f8a4c9c tests: isolate driver pacing policy inputs
Remove the real pacing hooks explicitly in the no-backend lane. NVAPI-capable D3D drivers can now satisfy the request, so relying on hardware refusal makes this lane fail on supported GPUs. Hold the focus callback true during synthetic backend checks so desktop activity cannot suspend them. Restore both driver interfaces before the threaded lane.
2026-10-19 06:04:15 +02:00
Codex b0cff07810 metal: add gated packed GPU frame imports
Copy cropped native sRGB BGRA/RGBA8 and linear BT.709 FP16 leases into persistent private textures on the context command buffer. Validate output format, explicit color metadata and the HDR linear pipeline before accepting the frame. Leave advertised import formats disabled pending native Apple acceptance and producer integration.

Let encoding callbacks refuse before ownership transfers, preserving the producer lease and existing texture when an encoder cannot be created. Add shipping-function tests for allocation failure, cropped copies, texture reuse, metadata refusal and retry, and extend the native Metal fixture to copy spatial patterns and FP16 values above reference white.
2026-10-19 05:55:43 +02:00
Codex 189aad0611 ci: account for driver pacing in runloop code budget
The upstream Driver Pacing release and latency-marker dispatch intentionally grows runloop_iterate to 6527 bytes in the menu-harness release build, beyond the old 6255-byte ceiling. Follow the existing budget policy by updating its code-size reference from 5004 to the measured size. Keep the 25 percent margin, stack reference and all other function budgets unchanged. No runloop or pacing behavior changes.
2026-10-19 05:55:43 +02:00
Codex 6d4a5cbed9 settings: keep driver pacing help within C90 limits
Preserve the driver pacing description while shortening its literal from 560 to 412 characters. The MSVC6 compatibility job treats C90 overlength strings as errors; the required supported length is 509 characters.
2026-10-19 05:50:28 +02:00
libretroadmin 23478f3dc4 d3d11, d3d12: Driver Pacing through NVAPI
The Direct3D 11 and 12 drivers get a Driver Pacing backend behind the
same pokes the Vulkan driver answers, on NVIDIA Reflex's NVAPI entry
points (NvAPI_D3D_SetSleepMode, NvAPI_D3D_Sleep,
NvAPI_D3D_SetLatencyMarker), which take any Direct3D device. nvapi is
resolved at runtime by its published interface ids and never linked, so
no SDK is needed to build, the oldest MSVC job builds as before, and a
machine without the driver's nvapi reports the device reason. The
sleep holds the thread for as long as the driver's own schedule says;
the mode is a property of the device it was set on and is taken off it
when the driver goes. The render and present markers are reported once
per released frame around the driver's content present, as on Vulkan,
and the shared state machine lives in gfx/common/d3d_common.c.

The Direct3D 12 driver drains its queue every frame today, so the
sleep can only move that stall; the backend is there so the driver's
own fence ring, when it comes, finds the release already in place.
2026-10-19 05:34:01 +02:00
libretroadmin d08a156604 video: Driver Pacing, the graphics driver's release of each frame, on Vulkan
A new Driver Pacing setting (video_driver_pacing: Off, On, On + Boost;
Off by default, so nothing changes until it is asked for) lets the
graphics driver say when each core frame should start, through NVIDIA
Reflex's VK_NV_low_latency2 on the Vulkan driver. The runloop holds on
the driver's own timeline semaphore where Frame Delay used to sleep,
before the frame's input is sampled, and reports the input, simulation,
render and present markers on the frame's identity so the driver can
place the release. Where the gap limiter or the content-rate timer
would hold, its period goes to the driver as the minimum present
interval rather than being slept a second time; vsync and audio keep
holding where they do, since the release schedules the start of a frame
and not its presentation. The mode is a property of one swapchain: a
replaced one starts over, and nothing names one the mode was not set
on. A backend error, or a sleep the driver never signals within the
bound the runloop allows, falls back to the existing pacing for good.

The release is owned only for a content frame the core made, on the
unthreaded path, at normal speed, focused, with the menu down; pause,
the menu and fast-forward keep their pacing, and Black Frame Insertion,
Shader Sub-frames, Scanline Sync and a swap interval above one keep the
mode off until their present schedule is mapped onto the extension's
one sleep between presents. The driver pokes (driver_pacing_set, _wait,
_mark) are vendor-neutral so other backends can sit behind them; the
requested mode and the reason it is or is not in effect show on the
statistics overlay and in System Information. The runloop's decision is
pinned in samples/runloop/pacing, the Vulkan backend's state machine
runs on fake entry points in samples/gfx/vulkan_driver_pacing (a new CI
job), and the runloop's side runs against a fake backend as a lane of
the threaded-video harness, on the null driver and on Vulkan.
2026-10-19 05:33:57 +02:00
Codex 42360a6d4d metal: retain synchronized GPU frame leases
Retain device-checked texture planes, backing objects and shared events across frame handoff. Encode readiness and final-use signals around consumer commands, and defer discarded or failed frames until producer readiness without accessing driver state.

Add geometry, lifetime and command-order fixtures plus a native Metal GPU check. Import formats remain disabled until producer and renderer integration is validated.
2026-10-19 04:59:49 +02:00
Codex 1839a82d45 gfx: preserve accepted GPU import retries
A retry may complete immediately instead of queuing. Clear the dropped result in that case so the producer is not told to reuse a frame the driver accepted. Cover every retry result and the already-retried and detached-player release paths.
2026-10-19 04:12:23 +02:00
Codex fb96179a90 metal: expose borrowed render device identity
Report the immutable Metal device through get_device without transferring ownership. Keep import formats empty until GPU-frame import and its resource lifetime contract are implemented.
2026-10-19 03:57:42 +02:00
LibretroAdmin 21cd608211 menu: Monitor Index lists only the connected monitors
The Monitor Index range follows the display server's output list
when Video > Output is opened: Auto plus one entry per monitor, with
the index chosen now kept in range. A server without an output list
keeps the full range. Harness: samples/menu/monitor_index.
2026-10-19 03:48:00 +02:00
Codex a646239dc7 gfx: enable capability-gated GLES texture loans
Reuse persistent upload buffers on GLES 3.1 contexts with EXT_buffer_storage and a resolved texture-level query. Older contexts and allocation failures retain owned-memory uploads. Request readable mappings for producers that preserve surface pixels.

Cover capability refusal, partial allocation, nonblocking slot retirement and exact Mesa EGL uploads, with native and sanitizer regression lanes.
2026-10-19 03:40:05 +02:00
LibretroAdmin d6d2e943cf cdrom: bound cue seeks and fill device paths with one snprintf
cdrom_device_fillpath() now builds each cue and track URI with a single
bounded snprintf and shares cdrom_device_fillimagepath()'s contract:
the output is always terminated and the required length is returned,
so a result >= len reports truncation.

CUE seeks take the same checked base-plus-offset path as the BIN and
cooked-image branches; an invalid whence or an offset that would
overflow or land before the start fails and leaves the position
unchanged. cdrom_cuesheet_overflow_test covers both under ASan/UBSan.
2026-10-19 03:31:11 +02:00
Codex 6007d55b6f gfx: lend renewable D3D11 staging maps to surface producers
Let native BGRA8 and FP16 surfaces write directly into independently
mapped upload slots. Add an optional per-write loan contract so direct
and threaded surfaces acquire the current pointer and pitch before reuse.
Persistent lenders retain their existing lifetime contract.

Keep map, unmap and upload work on the context owner. Refuse busy maps
without waiting and revoke incompatible maps to owned storage. Retain
replaced textures while a CPU producer still writes a loan. Read/write
maps support surface preservation without GPU readback; the GPU upload
copy remains.

Fence software decoder writes before publishing success or failure,
including partially written write-combined slots. Hardware job publication
is unchanged.

Cover remapping, padded rows, address reuse, cancellation, allocation and
map failures, fallback storage and deferred retirement with portable
fixtures, a threaded publication stress test and native GPU byte checks.
Update the integration producer to acquire writable storage and respect
its current row pitch. Wire the new fixtures into graphics and Wine CI.
2026-10-19 02:13:11 +02:00
Rob LoachandLibretroAdmin be9f97d2cf MCP: review fixes, checkpoints, queued STEPS (#19861)
* MCP: cold paused, held input, stacks, arg types

* MCP: checkpoints, prompt, completions, records

* MCP: completion errors, sized prompt, CI shaders

* MCP: rollback via loader, STOP n, completion fixes

* MCP: ask input_driver whether a replay records

* mcp: Clarify steps, and ensure MCP standards

* MCP: queued STEPS, checkpoint step, 32 steps

* MCP: queued STEPS say how long they sat idle

* MCP: queue ends on load/reset; simplify

* MCP docs: queue stops, idle, list caching

* MCP: shorter STEPS wording

* MCP: notice when port taken, --command, taps

* MCP: drop the play_game prompt and completions

* MCP: queue, tap and reset fixes from a review

* MCP: queue outcomes, hex values, field fixes

* command: --command takes an empty host or port as the default

"CMD;;PORT" and "CMD;HOST;" fill in the default host or port, so an
empty segment after a ";" counts as a host or port field again. A STEPS
list still keeps its own ";" steps, which carry a space, a "|" or are
"checkpoint".

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-19 02:12:59 +02:00
LibretroAdmin 7283c479ef Qt: resync float widgets against the value last pushed
A float spinbox or slider rounds a setting to its display precision,
so a stored value finer than that (slowmotion_ratio = 1.01 at one
decimal) is never what the widget reports back.  The paint-time
resync now compares the setting with the value it last pushed to the
widget, through ui_qt_float_resync(), so such a setting is pushed
once and repaints no longer redo widget work; the setting itself is
left as stored.

samples/ui/qt_float_resync drives the helper through model widgets
that round the way QDoubleSpinBox and QSlider do.

Closes #19849
2026-10-19 01:40:33 +02:00
LibretroAdmin ed3187ec8e android: fit the display cutout insets instead of resizing the window
With 'Enable fullscreen over notch' off on Android 15+, ask the window
manager to fit the display cutout insets rather than sizing the window
to the safe area by hand. The window manager then lays the window out
in the safe area in the same pass that rotates the display, so an
orientation change no longer shows a frame drawn under the cutout.
2026-10-19 01:26:14 +02:00
Codex 7b975b6cf0 media: finish inline player slot writes before presentation
Pair inline slot acquisition with slot_end before presenting or ending after feed, decode or FP16 rejection. Preserve the sink write lifetime needed for mapped upload buffers, and cover release ordering and termination with the scripted player tests.
2026-10-19 00:51:09 +02:00
Codex a4012b3040 Overlay: submit two-frame APNG canvases directly
Use the existing external-pixel surface submission for pressed and released APNG frames. Direct drivers avoid the redundant surface-slot copy, while padded upload loans use the surface row-pitch handling instead of a packed memcpy. Threaded uploads retain their owned slot and dropped states remain retryable.

Exercise the shipping overlay function with the real surface implementation on Linux and MinGW, including direct canvas identity, padded/spare loans, channel order, busy retries and queued ownership.
2026-10-19 00:21:24 +02:00
LibretroAdmin dfa3230587 Cocoa: run the menu while the Documents access prompt is up
When the first access to ~/Documents is still waiting on the system's
prompt, RetroArch starts with the user directories held: nothing on
the main thread makes or reads anything under them, so the window and
menu come up and run while the prompt waits. The prompt's answer
releases them - the builtin playlist folder is made, favorites and
history load, and the playlists tab is listed.

content_load_test boots the frontend held and checks both sides.
2026-10-19 00:11:15 +02:00
Codex 5b55ff4198 D3D11: preserve converted uploads without blocking Map retries
Convert packed 10-bit software pixels before busy UpdateSubresource uploads and initial image loads. Reuse texture-owned conversion storage instead of retrying a blocking staging Map, while retaining direct uploads for matching byte layouts.

Add portable failure/ownership checks and a real GPU oracle for idle and busy packed uploads.
2026-10-18 23:45:14 +02:00
LibretroAdmin 74277c7ce6 Cocoa: keep the window running through the Documents access prompt
The first access to ~/Documents raises the system's access prompt,
which blocks the accessing thread until it is answered. Touch the
folder on a thread of its own and run rarch_main once it returns, so
the main thread keeps handling events meanwhile; window delegate,
input and menu bar actions start with it.
2026-10-18 23:25:15 +02:00
LibretroAdmin 174f4bcdd5 Win32: restore a minimised window before a driver takes it
A window left up for the next video driver is handed over as it is.
Minimised, it has no client area, so a Vulkan driver taking it made its
surface for 0x0, the swapchain was refused, and the driver failed to
start - a device-loss rebuild with RetroArch on the taskbar left it
with no video driver. The kept window is now restored before it is
restyled and handed to the driver, as a new window would be shown.

The win32_window_proc harness gains a lane that keeps a minimised
Vulkan window and has the next Vulkan driver take it; its surface stub
refuses a 0x0 client as the real swapchain does.

Refs #19877
2026-10-18 23:21:49 +02:00
Codex 7681271958 D3D12: upload packed 10-bit frames without CPU repacking
Keep software XRGB2101010/HDR10 upload bytes in ABI order and interpret them through a frame SRV mapping with opaque alpha. Carry that layout through history and view textures, reset it for native GPU frames, and retain normal image conversion.

Admit packed-format frame memory and software framebuffer loans with correct SDR/PQ reporting. Preserve busy/pitch fallback and the existing GPU transfer.
2026-10-18 23:10:43 +02:00
Codex a739042d6f D3D12: preserve component mappings in shader bindings
Include the effective component mapping in each shader SRV cache key. Normalize default mappings and use the main source texture interpretation for frame overrides, while retaining resource ownership and descriptor reuse.
2026-10-18 22:11:39 +02:00
Codex df6c5e8e8e Video views: address FP16 software crops at full pixel width
Use eight-byte offsets for FP16 input in D3D11, D3D12 and GLCore views. Preserve the GPU-backed D3D12 copy path and existing SDR layouts. Cover padded source rows and crop origins with shipping-code fixtures and per-backend negative controls.
2026-10-18 21:36:46 +02:00
Codex 5b97dbdebd D3D12: sample menu channels through the texture view
Map native 16-bit menu channels in the SRV instead of rewriting borrowed upload memory on the CPU. Copied uploads preserve the same pixel layout. Clear the override on texture recreation and retain conversion for substituted storage formats.

Add a GPU oracle covering all 65536 color/alpha patterns through loads and linear sampling, with padded borrowed/copied uploads, 32-bit input and converted fallback. Keep its shader bytecode reproducible independently of the runtime HLSL compiler.
2026-10-18 21:11:42 +02:00
Codex 1a702f6fd0 D3D12: convert borrowed 16-bit menu pixels in place
RGUI BGRA4444 uses different bit positions from DXGI B4G4R4A4. Convert borrowed menu pixels in place before uploading, using the texture footprint pitch for direct loans and the aligned source pitch for pooled loans. Preserve the allocation and copy-free handoff without adding a GPU roundtrip.

Exercise the shipping DXGI conversion with distinct color/alpha values and padded rows; the regression fails before this fix.
2026-10-18 20:25:51 +02:00
Codex ea79555bc8 D3D12: lend aligned menu uploads through threaded video
Publish upload row alignment with each claimed menu descriptor and return the actual pitch to RGUI. Provision enough mapped storage for either menu format without adding a second request atomic.

D3D12 keeps menu uploads pending until their GPU copy is recorded, then retires them behind that submission fence. Superseded pending uploads can be reused; ready buffers release directly. Preserve converted-format and saturated-pool copy fallback, and the packed core-frame admission guard.
2026-10-18 20:03:06 +02:00
Codex 0e11f2bbd6 D3D12: lend retired menu texture uploads to RGUI
Reuse the existing two-slot texture loans for direct menu uploads. Return the native padded row pitch, decline converted formats and pending or in-flight slots, and publish mapped pixels without a bitmap copy. Preserve the copied fallback and release both upload buffers during teardown.
2026-10-18 19:42:15 +02:00
Codex 305f9506a0 D3D: track menu source formats separately from GPU formats
Texture creation may substitute a supported GPU format. Compare the requested source format with its own saved value so converted menu textures are reused across frames. Cover native formats and substituted formats in the allocation regression.
2026-10-18 19:33:56 +02:00
Codex 6a10c6bdf7 D3D: recreate menu textures when the pixel format changes
Include the source format in menu texture recreation checks on D3D10, D3D11 and D3D12. A same-size switch between 16-bit and 32-bit menu frames must not reuse a texture with the previous format.
2026-10-18 19:23:45 +02:00
Codex 07bd4ca641 Vulkan: lend retired menu uploads through threaded video
Provision a bounded mapped menu pool on the video worker and publish available buffers atomically. RGUI writes scaled pixels into a claimed upload; Vulkan copies from it on the GPU without an intermediate CPU bitmap copy.

Keep pending and GPU-held buffers unavailable until retirement. Handle replacement, resize and teardown, retain the CPU fallback on refusal or saturation, and exercise concurrent ownership under sanitizers.
2026-10-18 19:11:18 +02:00
Codex c68eea5598 Vulkan: require sampling features for native menu uploads
Admit packed 16-bit menu uploads only when optimal images support sampling and linear filtering. Otherwise decline loans and retain the BGRA conversion fallback. Exercise partial feature masks in the shipping-code regression.
2026-10-18 18:34:29 +02:00
Codex 0889cf1e73 Vulkan: lend mapped menu uploads to CPU producers
Let RGUI upscale directly into driver-owned menu storage at its native row pitch. Threaded video keeps its CPU loan, and unavailable formats retain the copy/conversion fallback.

Flush non-coherent menu writes while the allocation is mapped, before unmapping and submitting the upload.
2026-10-18 18:22:25 +02:00
Codex 5e4108c4b1 RGUI: upscale directly into threaded menu slots
Lend the producer-owned menu slot to the CPU upscaler and publish it without copying its bitmap. Reuse the existing triple-buffer handoff and retain local-buffer fallback when no loan is available.
2026-10-18 18:03:33 +02:00
Codex 29ec3c0390 Slang: expose native HDR ownership through Passthrough
Bind the optional uint from active output and native source or final-pass ownership. Explicit SDR final passes clear it even for PQ input; SDR-to-scRGB does not qualify merely by skipping inverse tone-mapping. Preserve SourceHDR independence and explicit float parameter priority.

Use renderer-owned per-pass constants and immutable zero for unsupported backends. No new image operation or synchronization is required.
2026-10-18 17:42:17 +02:00
CraigandLibretroAdmin b5217891e8 uwp: add msvc 2026 build (#19867)
* uwp: add msvc 2026 build

* CI: build MSVC 2026 next to 2022 instead of replacing it

The windows-2022 job keeps its UWP, 2019 and 2022 lanes and its action
pins. A separate windows-2025 job builds the 2026 solution and UWP with
the 2026 toolset; its artifacts carry a vs2026 tag so the two UWP lanes
upload under distinct names.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-18 17:40:54 +02:00
Codex 9869e5aa4e Tests: tolerate transient X connections in views pointer helper
Keep private Xvfb servers from resetting between clients, and retry the pointer helper connection within a bounded startup window. Log a permanent connection failure instead of silently exiting before pointer acknowledgement.

Exercise the real helper with successful, transient and permanently refused connections; include the regression in the SDL3 views job.
2026-10-18 16:56:45 +02:00
Codex 8bedd110f9 Video: preserve packed 10-bit framebuffer loan contracts
Report native SDR/PQ loan formats instead of XRGB8888 in the wrapper, Vulkan and desktop GL2. Keep D3D12 mapped wrapper buffers out of packed uploads that need channel repacking, matching its direct-loan restriction; CPU wrapper loans and ordinary packing remain available. Preserve compatible FP16 mappings and existing retirement, with no new wait or GPU readback.
2026-10-18 16:19:59 +02:00
Codex c189fec6de Slang: identify native PQ input through SourceHDR
Bind the optional uint from renderer-owned input state independently of output mode and final-pass ownership. Keep SDR, linear scRGB and imported movie textures clear, preserve explicit parameters, and define a typed zero fallback on backends without native PQ input.
2026-10-18 16:19:59 +02:00
LibretroAdmin f67b10f268 Ozone: keep parked thumbnails through content's close
A parked still stays parked while content is loaded: every request
for its path uploads it from the slot with the pixels lent to the
upload (gfx_surface_submit_image_borrowed, a submit for pixels the
caller keeps), so the close's driver reinit uploads the same decoded
image again instead of reading and decoding the file, and the cover
is back the frame the menu is. The slot goes at the first frame after
the close, or at the completion of an upload still reading it.

Lane 15 of gfx_thumbnail_anim covers the close's request and a queued
upload outliving the close; the two thumbnail harnesses' display-size
and viewport stubs now answer bool as the frontend declares them, so
the downscale cap compared at park and unpark is no longer a stale
register.
2026-10-18 16:16:34 +02:00
Codex 0dfc8ecb14 Slang: expose the rational HDR TonemapOperator
Bind optional uint TonemapOperator to the current rational curve through shared reflection and declared-only GLCore/Vulkan writes. Preserve explicit parameters and keep curve identity independent of conversion enablement and final-pass ownership.
2026-10-18 15:26:43 +02:00
Craig 2b167e84cb webOS: update SDL3 pre-built binary (#19866) 2026-10-18 15:11:44 +02:00
LibretroAdmin cd472266ec Revert "ci: use downloaded gradle for android jobs (#19820)" (#19876)
This reverts commit 12ca52c2d3.
2026-10-18 15:11:29 +02:00
Codex 2c11b9577d Slang: expose unknown minimum luminance through MinNits
Report zero while minimum luminance is unknown, matching HDR metadata. Bind immutable fallback storage in shared reflection and declared-only zero writes in GLCore/Vulkan. Preserve explicit parameters and cover storage lifetime, buffer writes and native image acceptance.
2026-10-18 06:21:00 +02:00
Codex 5b2fda07ad Slang: expose active HDR output through EnableHDR
Bind the optional scalar float from each renderer's actual HDR mode. Preserve explicit parameters and keep HDRMode as uint. Cover both loaders, inactive output, Metal view rebasing and native image checks.
2026-10-18 05:58:25 +02:00
Codex 3d6907eb56 PSL1GHT: avoid output_mode macro in HDR compatibility helper
Use a distinct argument name so the SDK output_mode-to-mode alias cannot collide with the local mode variable. Exercise the real SDK alias in the ownership harness as well as the normal C89 path.
2026-10-18 05:58:07 +02:00
Codex cb311c274d Slang: carry effective HDR peak through frame snapshots
Populate optional PeakNits and MaxNits floats on D3D11/12, Vulkan, GLCore and Metal. Capture the selected display override or configured target once in frame info, then use private pass/chain state and clear it without active HDR. Preserve independent pragma parameters and both reflection locations without pixel transfers or new synchronization.
2026-10-18 05:04:07 +02:00
Codex a3ebc76caf Slang: recognize the PaperWhiteNits alias
Bind the optional float alias to existing paper-white state on D3D11/12, Vulkan, GLCore and Metal. Keep separate reflection locations when both spellings are declared, preserve explicit pragma parameter precedence, and retain existing semantic IDs. No pixel transfer or new frame-path allocation is needed.
2026-10-18 04:50:25 +02:00
Codex 66a1ef084c Metal: bind Slang HDR conversion controls
Expose InverseTonemap and HDR10 from the existing render-owned compositor policy through private pass constants. Clear both without active HDR or compositor uniforms. Exercise the shipping conversion policy for SDR, PQ and scRGB output, including internal PQ-to-scRGB conversion and per-view rebasing.
2026-10-18 04:26:42 +02:00
Codex 50ef1f8581 video_thread: keep cached replay out of the hot handoff
Move the video-thread-only replay path into a noinline helper. This preserves cached-frame conversion, cropping and hardware-slot handling while reducing video_thread_frame from 4913 to 4257 bytes and its stack frame from 216 to 200 bytes in the Linux menu build. Keep the existing hot-function budget unchanged.
2026-10-18 04:24:12 +02:00
Codex ad0a9ae5de Metal: bind Slang scanline and subpixel controls
Use the existing render-owned compositor latches for declared Slang uniforms. Clear both values without active HDR and keep per-view constants in their private pass storage.
2026-10-18 04:06:03 +02:00
Codex c9efa8a075 Metal: snapshot Slang constants for queued draws
Keep mutable CPU staging separate from encoded GPU constants. Use copied encoder bytes below 4 KiB and a command-retained immutable buffer for larger payloads, so later frames and placements cannot overwrite queued values.

Reject failed captures and suppress incomplete view output without waiting for GPU completion. Cover queued staging reuse, stage bindings, boundary sizes and allocation refusal with the shipping binding code.
2026-10-18 03:49:59 +02:00
Codex 5c712c87b3 Metal: populate existing Slang HDR uniforms
Bind HDRMode, BrightnessNits and ExpandGamut from the negotiated output and frame settings through the existing uniform buffers. Reuse per-view pass ownership and pointer rebasing, with neutral values for unavailable HDR.

Keep the exposed mode aligned with published output when colorspace setup fails. Add portable shipping-code checks for bindings, frame handoff, output fallback and isolated views.
2026-10-18 03:39:53 +02:00
Codex 7d617e9f68 D3D: bind shader HDR metadata to active output
Use the renderer-owned swapchain encoding for HDRMode and clear output metadata when HDR is inactive. Preserve the shader conversion policy across output transitions.

Exercise requested/active disagreement, HDR-to-SDR clearing and compiled-out HDR with shipping-code fixtures. Extend SDR image checks to D3D11 and D3D12.
2026-10-18 03:22:34 +02:00
Codex 8e993f4fe2 Slang: declare final-pass HDR ownership in presets
Accept optional hdr_mode declarations while preserving legacy inference and existing shader parameters. Keep target precision and frontend conversion aligned with declared SDR, PQ or scRGB output; reject incompatible active HDR outputs.

Preserve ownership through reference overrides and saves. Sample the returned D3D12 final-pass texture when scaling creates a separate target. Add parser, serialization and renderer acceptance coverage without CPU pixel conversion or new frame-path synchronization.
2026-10-18 03:04:20 +02:00
Codex 7ef299dd3c GLCore: populate existing Slang HDR uniforms
Bind HDRMode, BrightnessNits and ExpandGamut through the existing typed uniform paths. Use actual renderer output and frame-carried settings, retaining zero values on SDR and refused HDR contexts. Keep the values with the chain so deferred passes observe the same state without texture copies or readbacks.

Add native UBO/push-constant image checks across the existing HDR backends and an SDR/fallback runner.
2026-10-18 02:14:24 +02:00
Codex 99246ab065 Submit native FP16 scRGB core frames through HDR video drivers
Negotiate canonical 80-nit RGBA16F input for scRGB output on D3D11, D3D12, Vulkan and GLCore. Preserve eight-byte rows in the threaded ring and framebuffer loans, native GPU uploads, signed values and stock presentation without SDR expansion. Reject unsupported output modes and raw recording/filter paths.

Forward native HDR screenshot readback on the video thread after the claimed frame. Correct D3D12 FP16 copy footprints and failed sprite mapping, and keep queued game/UI composition constants in separate existing buffers. Add native copied/lent, stock, overlay, deferred and geometry-change acceptance fixtures.
2026-10-18 02:00:25 +02:00
Codex 03040ef17e Vulkan: use the requested Android swapchain extent
Android permits imageExtent to differ from currentExtent. During an
orientation change, currentExtent can still reflect the previous
swapchain when the new content dimensions arrive. Using it then leaves
the viewport and swapchain at different aspect ratios.

Use the requested dimensions for Android, clamped to the surface limits.
Keep fixed-extent selection on other platforms and the existing render
path, without adding image copies or intermediate targets.

Add a device-independent regression test for repeated orientation changes
with stale extents, variable extents, clamping and unavailable surfaces,
and run it in CI.

Fixes: https://github.com/libretro/RetroArch/issues/19869
2026-10-18 01:43:34 +02:00
Codex 677e6df54c Define FP16 scRGB core pixel format
Specify binary16 RGBA sample order, native endianness, byte pitch, ignored alpha and canonical 80-nit linear scRGB units. Link the format to its source capability bit. Negotiation remains rejected until the native frame path is implemented; output framebuffer precision is not input support.
2026-10-18 01:21:05 +02:00
Codex 5f9d1c6198 Restore UTF-8 punctuation in GLCore comments 2026-10-18 01:19:06 +02:00
Codex 2ac81fd19d Publish D3D texture capabilities without renderer flag races
Main-thread scRGB queries read published HDR state instead of mutable D3D11/D3D12 renderer flags. Make D3D11 lazy planar failure publication atomic as well. Preserve feature-level and pipeline admission and exercise the shipping publishers/getters with HDR enabled and disabled.
2026-10-18 01:18:33 +02:00
Codex 6d6da52ceb Publish GLCore scRGB texture capability without framebuffer races
Publish native scRGB texture admission when framebuffer layout changes. Main-thread queries consume the atomic snapshot instead of render-thread flags and framebuffer handles. Preserve the FP16 UI requirement for PQ sources and recording fallbacks. Exercise shipping functions concurrently; the old getter reproduces a TSan race.
2026-10-18 01:09:39 +02:00
Codex b81c97ab11 Make HDR source format capabilities extensible
Replace the 10-bit boolean with a fixed-width native input format mask. Distinguish packed HDR10 from reserved FP16 linear scRGB input without claiming support from output framebuffer precision. Keep HDR10 negotiation and passthrough tied to their explicit format bit.
2026-10-18 01:03:07 +02:00
Codex 0c22c4f74e Publish Vulkan texture capabilities without render-state races
Cache YUV420 and scRGB admission in an atomic mask when pipelines are built. Main-thread format queries no longer inspect mutable presentation flags or pipeline handles. This adds no GPU queries, frame copies or waits.

Exercise the extracted shipping query and publisher with concurrent rebuilds, both HDR variants, and native plus ASan/TSan runs. The old query reproduces the TSan race in the same fixture.
2026-10-18 00:59:28 +02:00
Codex 59927f2359 Read viewports after drawing the claimed frame
Service viewport readback commands after the frame already claimed by the video thread. Reading first returns an older image even though a newer frame has been taken out of the pending ring.

Keep other command ordering unchanged and add no wait or pixel copy.
2026-10-18 00:59:28 +02:00
Codex bd86b8a038 Respect widget ownership during visibility queries
Ignore inactive widgets and apply the same video-thread ownership guard used by widget drawing. This prevents visibility checks from reading widget state after teardown has begun.

Cover inactive and retired visibility snapshots, initialize directly submitted test frames, and establish an actual frame in flight before testing widget teardown.
2026-10-18 00:59:28 +02:00
Codex 1b3f7ce414 Preserve shader swap count during inline video replay
Stamp inline frames with the video-thread-owned swap counter, as ring-delivered frames already do. Cached screenshot replay must not reset the shader uniform or count an additional present.

Exercise stale inputs and a counter above the 32-bit range through the shipping wrapper.
2026-10-18 00:59:28 +02:00
LibretroAdmin b9504b89b0 Report the display size through GET_DISPLAY_METADATA
The display server's pixel and millimetre size is cached whenever a
display server comes up, at the early startup init and with each video
driver, and GET_DISPLAY_METADATA fills geo.native_width/native_height
and width_mm/height_mm from it. The cache outlives video driver
teardown, so a core asking from retro_load_game sees it, and a server
that reports no size, like the null one, leaves the last report in
place. Win32, X11, Wayland and UWP report a size; elsewhere it stays
zero.

x11_get_metrics returns false when XOpenDisplay fails instead of
reading through NULL, which the startup query reaches whenever DISPLAY
is set with no server behind it. samples/gfx/hdr_metadata covers the
geometry path and display_servers_test the failed open.
2026-10-18 00:44:50 +02:00
RyunamandLibretroAdmin df9266a425 Ozone: Fix thumbnail delays when starting/closing content (#19872)
* Ozone: Fix thumbnail delays when starting/closing content

* Ozone: park content-switch thumbnails instead of copying them

Stills requested as content starts land decoded and are held by path
(at most four) instead of being uploaded; the next request for the
path uploads the held image without reading or decoding the file.
Starting content now decodes each cover once and uploads nothing,
closing it uploads from the held image, and no copy is made. Held
images go once content is closed or the menu is torn down.

Stubs for the runloop queries in both thumbnail harnesses, and lane
15 in gfx_thumbnail_anim covering park, take-back, drop on changed
decode settings and free at close.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-17 23:46:49 +02:00
Codex d0260a45f3 Write HDR capture directly to P010 plane UAVs
Use typed P010 UAVs when supported to avoid intermediate plane textures and two GPU copies per frame. Keep a per-slot fallback when capabilities or resource creation reject direct access.

Exercise direct, fallback and mixed pools with real GPU reference checks and independent HEVC/AV1 decode.
2026-10-17 23:14:02 +02:00
Codex 739016c8b9 Fix D3D Slang HDR uniform bindings
Bind SwapCount at its semantic index instead of shifting the HDR uniforms. Cover immediate and deferred loaders on D3D10, D3D11 and D3D12, with and without DXGI HDR.
2026-10-17 22:40:19 +02:00
Codex e866e894d6 Replace HDR queries with versioned presentation metadata
Retire experimental queries 88-92 and add bounded HDR/display queries on unused command IDs. Publish configured output state without renderer roundtrips and migrate the builtin media core. Submit DXGI metadata per swapchain to remove the shared-cache race and missed reapplication.
2026-10-17 21:48:08 +02:00
Codex bc6ee1349c Convert D3D12 HDR capture to P010 on the GPU
Convert PQ RGB10A2 and linear scRGB to PQ P010 with viewport cropping, range conversion and left-sited chroma. Preserve source states and fence leases without CPU pixel readback or capture waits.

Validate GPU planes against a double-precision reference, independently decode HEVC/AV1 output, and exercise HDR swapchain retirement. Keep HDR frontend admission disabled pending metadata and frontend acceptance.
2026-10-17 20:23:18 +02:00
Codex b35eb19c13 Preserve native recording channels through playback folds
Keep the original multichannel batch available to recording when playback uses the stereo fallback, without retaining it after the callback. Admit native AC-3 speaker masks up to six channels while preserving their positions.

Use the configured float hint before a device context exists, even if the audio active flag is already set. Add channel-identity and startup-format regressions plus native signed-PCM and negotiated-float acceptance.
2026-10-17 19:41:26 +02:00
Codex a86a67fdda Record stereo core audio with the native NVENC frontend
Enable optional AC-3 from the existing signed PCM recording path at 32, 44.1 and 48 kHz. Arm audio and video together after worker startup, preserve their shared clock, and fail on PCM backpressure or rate changes without blocking or dropping audio.

Let already queued video frames complete after normal stop so the audio tail stays aligned. Cover admission and retirement boundaries in portable tests and validate all codec/threading/rate combinations through the real frontend.
2026-10-17 19:06:39 +02:00
Codex 33e766ee40 Connect native D3D12 NVENC recording to the frontend
Expose an explicit video-only NVENC MP4 recording driver for SDR D3D12. Carry GPU capture and timestamps with direct or threaded video frames, then retire the owned capture asynchronously after queued frames release it. Defer startup until the viewport is ready and keep staged quit alive through finalization.

Add real frontend codec acceptance and frame-rate and wrapper lease regressions. Core audio and HDR admission remain unavailable in this mode.
2026-10-17 18:40:42 +02:00
Codex 9285db09db Let D3D12 recording dispatch own capture retirement
Transfer a fresh capture only on successful construction. Retire it on the I/O worker after producer closure and session teardown, preserving cancellation while GPU work drains.
2026-10-17 18:10:58 +02:00
Codex 71445e9d30 Test D3D12 capture retirement across swapchain resize
Verify BGRA and RGBA backbuffer references retire independently of encoder destination leases. Exercise resize rejection before reap, reuse after resize, and final producer closure with system and bundled D3D headers.
2026-10-17 17:42:47 +02:00
Codex 1ec4bd61c9 Retain D3D12 capture sources through GPU completion
Keep submitted textures and source fences alive when callers release their own references, retaining replacements before retiring earlier leases of the same objects. Add nonblocking source retirement independent of encoder destination leases so swapchain owners can release completed references before resizing. Verify early caller release, replacement ordering, pool backpressure, wait failure and signal recovery against native GPU submissions.
2026-10-17 17:24:57 +02:00
Codex f7c03ea8d6 Validate native D3D12 recording dispatch
Exercise dedicated session startup, concurrent GPU capture and PCM submission, drain and collection with the native hardware harness. Compare MP4 video and decoded audio with elementary-stream and independent AC-3 references across H.264, HEVC and AV1. Retain the existing direct output cases and document the remaining frontend integration work.
2026-10-17 17:00:32 +02:00
Codex 90235d8113 Dispatch D3D12 recording sessions on a dedicated worker
Keep session startup and I/O retirement off render and audio paths. Register before rechecking input, wait on producer/completion events, and schedule SDK BUSY retries without polling an idle session. Preserve independent producer closure and nonblocking collection.

Exercise park races, concurrent producers, cancellation and composed session retirement on native Windows and the Linux scheduling shim under sanitizers.
2026-10-17 16:41:33 +02:00
LibretroAdmin 9b99bb997c openxr: target the headset's rate only while the runloop runs at it
GET_TARGET_REFRESH_RATE answered the headset's rate whenever one was
shown. A core that follows the answer then ran at 120 Hz on a 60 Hz
config although the headset paces it at 60 with each frame shown
twice, and rendered twice the frames the headset can take.

The runloop iterates at the headset's rate only on a headset's own
context with no fitting interval and no wrapper: a fitting headset
paces the core at its own rate, a window's vsync paces at the
configured one, and the wrapper holds the core at its own. That rate
is decided where the interval is, as headset_runloop_hz, and the
environment call reports it. A lane in the threaded_video harness
covers the headset's context, a window and the wrapper at 90 and 120
Hz against a 60 Hz config.
2026-10-17 16:41:04 +02:00
Codex 8fe615136c Own D3D12 recording session startup and retirement
Keep encoder, packet pump and output ownership on one I/O worker. Drain cancellation and partial startup failures without freeing active producers, pending file borrows or encoder leases. Publish terminal results only after retirement; leave capture GPU lifetime with its owner.

Cover producer closure, destruction refusal and concurrent PCM retirement under sanitizers, including threaded and threadless builds.
2026-10-17 16:19:24 +02:00
Codex 6ec910ea31 Expose GPU recording retry state for asynchronous scheduling
Distinguish outstanding encoder or file work from idle producer waits when no completion handle is available. Preserve cancellation drain ownership and cover submit/EOS backpressure and idle PCM tails.
2026-10-17 15:58:37 +02:00
Codex c58d703a2e dxgi: report presentation backpressure before running the core
An unthreaded core with vsync and audio sync disabled can submit frames
faster than the shared presentation target is consumed. The bridge
reported itself presentable while its current target was still pending,
allowing seconds of GPU queue latency to accumulate.

Report a busy target through presentable() so the runloop's existing
no-surface pacing defers the next core frame. Query the completed fence
value without waiting or consuming a frame-latency token. Preserve
occlusion recovery and let a removed device reach the fallback path.

Exercise pending target admission, recovery and device removal. Refresh
the DXGI regression fixtures to match the current production functions.
2026-10-17 07:44:42 +02:00
LibretroAdmin 6654c71e92 samples/menu/network_settings: Minimum Audio Latency under Latency
The harness opens Settings > Latency and checks Minimum Audio Latency
is listed there, then picks 1, 2 and 16 from its list and checks each
sets the value picked, and that 0, below its minimum, is not offered.
2026-10-17 06:09:35 +02:00
Codex 4f228f0d7d Install FFmpeg tools for recording CI oracles
The MP4 and GPU A/V reference tests need ffmpeg and ffprobe executables. Development libraries alone do not install those tools.
2026-10-17 06:00:46 +02:00
Codex 80d2546cfe Fix console ring sample linkage after multichannel audio changes
Link the channel-layout helpers and provide the fixture stereo layout request. The PS4 driver now needs both when opening its output port.
2026-10-17 04:51:21 +02:00
Codex 4375f0c249 Revert threaded GL handoff changes for regression isolation
Restore the handoff behavior preceding 7fa57eaf2b while investigating reported responsiveness regressions and recurring NVIDIA faults. Retain the subsequent AC-3 build fix. This rollback does not establish a fix for the original GPU crash.
2026-10-17 04:50:57 +02:00
Ryunam be50f8018c Menu: Fix 'Minimum Audio Latency' not showing under Latency sub-menu (#19818) 2026-10-17 05:53:22 +03:00
Codex 8146d81dd9 Expose GPU recording completion handles for worker scheduling
Borrow the oldest encoder completion or pending file handle without consuming state. Forward the active dependency through the output pump, retaining file readiness during abort drain. Callers also arrange producer notifications and SDK retry scheduling. Exercise completion waits in the native GPU A/V and disk tests.
2026-10-17 04:21:37 +02:00
Codex 7cc9c23dc4 build: avoid duplicate AC-3 frame object with D3D12
AC-3 is included unconditionally. Include the D3D12 recording output with it rather than testing an unset make variable and adding the frame parser a second time.
2026-10-17 04:15:51 +02:00
Codex 7fa57eaf2b gl: keep threaded hardware handoff nonblocking
Select an unqueued, completed target before publishing queried GL frames.
If none is available, retain the producer's target and account for a
missed presentation instead of waiting for the presenter or GPU.

Return source fences through atomic exchange and retire them on target
reuse, rather than deleting them immediately after a cross-context wait.
Only successful completion permits reuse. Superseded disposable GL
completion fences do not need a blocking wait before replacement.

Preserve cached-framebuffer compatibility and other hardware APIs.
2026-10-17 04:08:23 +02:00
libretroadmin b3dcba0388 audio: multichannel output for waveout, orbis and rwebaudio
waveout opens a wider layout as WAVEFORMATEXTENSIBLE with the
frontend's mask and falls back to stereo where the device refuses it.
orbis opens sceAudioOut's eight-channel port when a wider layout is
asked for and reports 7.1, leaving the downmix to the system.
rwebaudio widens the page's destination to 8, 6 or 4 discrete
channels as far as its maxChannelCount goes and queues one plane per
channel.

All three report the layout they opened through the layout hook; with
stereo asked for, or a device that takes only stereo, each opens as
before.
2026-10-17 04:08:09 +02:00
libretroadmin 2dd201ab2c audio: hand ALSA devices their own speaker order
Both ALSA drivers now take the device's channel order from its channel
map - snd_pcm_get_chmap() for alsa-lib, the card's "Playback Channel
Map" control for tinyalsa - and fall back to ALSA's own FL FR RL RR FC
LFE SL SR order where there is none. The frontend's frames are permuted
into it on the way out, in a stage for write() and in place in a lent
mmap span, so an HDA card no longer plays the centre from the rear left;
a device already in the frontend's order costs nothing.

A 7.1 request on a narrower card now opens 6 or 4 channels before
stereo in both drivers, and tinyalsa reports the layout it opened rather
than stereo. alsa_write() advances by the frame size after a short
write instead of assuming two channels.
2026-10-17 04:08:05 +02:00
Codex 836a6e2c57 Fix UWP mux linkage and process barrier API selection
Link the AC-3 frame parser for D3D12 builds without the AC-3 codec. UWP uses FlushProcessWriteBuffers directly and excludes the VirtualLock-dependent page-flip fallback. Compile the barrier with the UWP API partition in Windows CI.
2026-10-17 03:35:59 +02:00
Codex 5d7caa9541 Combine GPU packets and PCM in bounded native recording output
Feed the GPU worker and a whole-batch PCM SPSC handoff into native AC-3,
MP4 and overlapped file output. Stage partial audio blocks so small queues
can accept varying callback sizes without waiting for a full AC-3 frame.
Pad only the closed tail, preserving the audio sample clock and video ticks.

Retain mux output across disk backpressure and cancellation. Successful END
requires both streams drained and the I/O worker's file flush/close. The
caller retains GPU teardown ownership on errors. Frontend capture, core
audio callback routing and worker scheduling remain separate integration.

Exercise concurrent close/drain and file/codec errors, decode portable A/V
fixtures, and validate native capture/encode/PCM/file sessions against raw
video and independently encoded audio references.
2026-10-17 03:27:01 +02:00
Rob LoachandLibretroAdmin 7f86c7f53c MCP: STEPS, PRESS until, and fixes after WATCH (#19850)
* MCP: answer once when reply_to answered already

* Commands: refuse slots past INT_MAX

* MCP: follow-ups to WATCH and the folded tools

* Commands: take CRLF lines; keep long family names

* MCP: fixes from a review of the server

* stdio_e2e: tool list size after the combined fixes

* MCP: lone CR, over-long stdio lines, slot cap

* MCP: stdio keeps its agreed revision

* docs: MCP server doc matches the code again

* Leave CHANGES.md out of this change

* MCP: STEPS, PRESS until, 16-bit waits, 16 watches

* MCP: 4 kB off tools/list; CORE; LOAD_CONTENT paused

* MCP: refuse unknown params; taps; review fixes

* MCP: GET_LOG, READ, RUN_UNTIL say what they mean

* MCP: LOAD_CONTENT's frame back under budget; STEPS counts its taps

LOAD_CONTENT copies the content path, to the heap, only where a pause
setting follows it, which keeps its stack frame where it was. STEPS
counts the frame let go between two presses of a button against the
frames one request runs, so a list it accepts can finish in them.

Lane: the taps case in samples/network/command_frames.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-17 03:14:30 +02:00
Codex 1ac0ec533c Write native recording output with bounded overlapped I/O
Retain each MP4 output borrow until its overlapped write completes. Handle
short writes with one continuation per service step, propagate disk errors,
and keep cancellation leases until terminal completion, including races.
Create new UTF-8 named files without overwriting existing recordings.

Flush and close only on the owning I/O worker after drain. No thread or
frontend mode is installed; scheduling and live audio remain separate.
Exercise ownership and failure paths under fault injection, then decode
Windows MP4/AC-3 fixtures written through the production writer.
2026-10-17 02:58:31 +02:00
Codex b60ea07094 Mux native recordings into bounded MP4 fragments
Build H.264, HEVC and AV1 sample descriptions from encoder headers and
fragment access units with optional native AC-3 audio. Retain bounded output
until acknowledgement, without frame-path allocation, file I/O or readback.

Validate native NVENC MP4 counterparts and portable A/V files through
independent decoding, timing, keyframe, backpressure and prefix tests.
2026-10-17 02:27:47 +02:00
Rob LoachandLibretroAdmin 45c6704fed sdl3: overlay behind menu and faster key lookup (#19823)
* sdl3: overlay behind menu, faster key lookup

* sdl3: key events name the unmodified key of their scancode

The scancode table now holds the RETROK of each scancode's unmodified
key, from the layout, so the 1, 4, 7, 8, 9, 0, =, ; and ' keys are
themselves again instead of the symbols shifted onto them.

The write wait looks at the room a wake freed before counting the
lap, and overlay_behind_menu is read where overlays are built.

Lanes: samples/input/sdl3_keyboard, and the write-wait case in
samples/audio/sdl3_raw.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-17 02:29:15 +02:00
Rob LoachandLibretroAdmin d93ee63d0c sdl3: pad slot, cursor, microphone and draw-path fixes (#19822)
* sdl3: pad slot, cursor, mic and draw-path fixes

* sdl3: keep sensor state on a rejected pad, trim the mic to its latency

A pad turned away on webOS frees its slot but keeps the sensor state
asked of it, as a disconnect does. Past its cap, the capture backlog
drops its oldest frames down to half the cap, the requested latency,
so a stall does not leave the capture a whole cap behind.

The display draw geometry is typed and sits with the other pointers.
Lanes: joypad_bridge_checked/sdl3_webos_reject_test and the trim case
in sdl3_mic_wait.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-17 02:16:09 +02:00
Codex 730426c87e Service GPU recording through a bounded NVENC worker
Retain queue descriptors across encoder backpressure and packet bytes until
downstream acknowledgement. Return texture leases after unlock and submit
EOS only after producer close, without worker waits or frame-path readback.

Exercise the production pump with native encode/capture oracles and portable
retry, saturation, EOS and teardown tests in threaded and threadless builds.
2026-10-17 02:06:36 +02:00
Rob Loach 42ae846457 sdl3: C89 build fix, font batch off the stack (#19821) 2026-10-17 02:03:44 +02:00
Codex d185c1f6bf Add bounded D3D12 GPU capture for native recording
Capture viewport RGB and full-frame NV12/P010 into leased encoder textures. Convert RGBA to BGRA on the GPU, restore source state and publish completion fences without pixel readback or CPU waits.

Recycle command resources only after completion and packet release. Quarantine a failed signal until a recovery fence completes. Validate captured output against direct input and exercise failure retirement on a real GPU.
2026-10-17 01:49:29 +02:00
Codex 087c08fb85 Preserve static HDR metadata in native NVENC output
Serialize optional mastering and light metadata in HEVC SEI and AV1 metadata units. Keep metadata immutable across encoder retries and include it at requested and periodic random-access frames.

Validate independent bitstream fields, decoded keyframe restarts, absence and bounds across both D3D12 SDK header variants.
2026-10-17 01:17:32 +02:00
LibretroAdmin 2806bb5a18 led: drop the driver on free, log the driver chosen
led_driver_free() leaves no driver behind, so nothing can reach a
freed one before the next led_driver_init(). The init log names the
driver actually chosen, by its ident, and stays quiet for the null
driver, an unknown name included. A compile-time check keeps MAX_LEDS
within the output store's slots.

samples/frontend/led_driver checks that a freed driver is not
called and what the init logs.
2026-10-17 01:06:09 +02:00
Derek Eskens ccc9b77f7b samples: test the LED driver's store and flush
led_driver.c stores a core's LED calls and writes them to the driver
once the core has run. Nothing tested that wiring: the index range,
the 0/1 normalisation, the drop on init, the flush before free and
the null-driver fallback.

The test builds led_driver.c from the tree with HAVE_OVERLAY and
stands in for the overlay driver with a recorder, including two
threads setting LEDs while the main thread flushes. It also pins that
led_driver_free() leaves the driver chosen.

Builds with mingw-w64; Linux-samples-unclaimed runs it.

Closes #19854
2026-10-17 01:06:09 +02:00
Codex 11ecd89030 Add bounded GPU recording slot handoff
Pass slot, fence-value and timing descriptors from a producer to an encoder worker through SPSC queues. Retain busy input and recycle textures only after packet release; deliver EOS before GPU output drains. Exercise the handoff with separate native NVENC producer and encoder threads. Frontend capture, worker scheduling and mux/audio integration remain pending.
2026-10-17 00:52:55 +02:00
LibretroAdmin 71979e20a0 Windows: enumerate optical drives through the shared device path
Build the enumeration path with cdrom_device_filldevicepath(), the same
constructor cdrom_drive_has_media() uses, so drive identification always
opens a metadata-only handle and issues INQUIRY alone.

dvd_blocks_test gains a Windows enumeration lane (run under the existing
mingw+wine step): two optical drives and one fixed disk are mocked, and
the lane requires two INQUIRYs and no TOC reads.
2026-10-17 00:48:49 +02:00
Ryunam d2981e4a79 Windows: Fix startup speed regression due to CD-ROM code (#19859) 2026-10-17 00:47:53 +02:00
libretroadmin 633c4cadd5 Never used, cleanup 2026-10-17 00:37:23 +02:00
Codex 7bb25a07c2 Fix NVENC resource descriptions with modern Windows SDKs
Use the explicit COM return buffer on MSVC as well as MinGW. New Windows SDKs declare GetDesc with an output parameter, so the aggregate-return macro broke the ARM64 Griffin build. Native MSVC tests validate both system and vendored D3D12 headers.
2026-10-17 00:10:53 +02:00
Codex c145eb0c92 Signal input color characteristics in native NVENC streams
Carry BT.709 or P010 BT.2020 PQ/HLG primaries, transfer, matrix and range into H.264/HEVC VUI and AV1 sequence headers. Reject invalid HDR input combinations before opening the device. Verify stream tags independently across both D3D12 header ABIs; preserve SDK defaults for existing callers.
2026-10-16 23:58:28 +02:00
Codex 985e0d94bf Add a native D3D12 NVENC encoder adapter
Retain registered GPU input slots through asynchronous H.264, HEVC and AV1 encoding, using ready/completion fences and nonblocking packet retrieval. Preserve busy-call ownership and drain EOS before teardown. Native reference checks cover BGRA, NV12, P010 and both D3D12 header ABIs. Frontend capture, recording and muxer integration remain pending.
2026-10-16 23:48:36 +02:00
Codex 664f389644 Export Vulkan decoder reference images without copying
Use unified video layouts to sample DPB planes directly. Rename physical slots while exported frames remain held, preserve GENERAL in both importer paths, and retain copied output when the feature or sampled storage is unavailable.
2026-10-16 23:08:44 +02:00
LibretroAdmin a8c052581b ozone: animate the list scroll only alongside a cursor fade begun this iteration
With sublabels shown for the selected entry only, the compute pass
animates the scroll when a cursor fade has just started. That is now
a flag set where the fade is pushed and cleared at the end of the
render, so a compute in a later iteration, or after a selection change
that asked for no animation, moves the list at once whatever the
animation clock has done to the cursor alpha.

samples/menu/ozone_sublabel_scroll drives the real handle with the
clock held still: the jump, the tween and its end, a later compute
during the fade, the list behind the sidebar following a new
selection, and a selection past the end of the list. Run by the menu
harness workflow.
2026-10-16 22:56:09 +02:00
Codex 5f63282c6f Decode streaming surfaces into padded upload rows
Carry the backend upload pitch through surface lending, threaded completions and per-job decoder output. Packed producers retain their existing contract, while canvas copies and swizzles honour each destination stride.

Repack replacement loads and preserve ready or still-writing frames until their borrowed texture can be released. Cover padded RGBA8/FP16 rows, slot rotation, replacement lifetimes and native D3D12/Vulkan captures.
2026-10-16 22:47:06 +02:00
Codex 3ee463e16c Allow padded rows in video decoder output
Add an explicit pixel stride for MP4/WebM caller output, including FP16 and pooled row conversion. Keep the packed setters and canvas layout unchanged, and reject short or overflowing output layouts before changing the destination.
2026-10-16 22:08:59 +02:00
Ryunam e6b621af38 Ozone: Fix more UI glitches when showing sublabels only for selected entries (#19857) 2026-10-16 22:05:01 +02:00
Codex 8fc5ca6633 Retire running thumbnail previews without blocking reset
Cancel queued preview work without waiting for a running decode. Retain
the player, stream, feeder, mapping and producer surface until polling
observes worker completion. Give the feeder independent callback state
so thumbnail storage can be freed immediately.

Keep the existing worker teardown barrier before the menu-reuse early
return, so retired GPU sessions settle before their device is destroyed.

Held-feeder and retained-texture tests reject blocking reset and premature
context teardown. Native Vulkan playback and reused-menu teardown pass
with direct/threaded video on Windows and Linux.
2026-10-16 21:48:00 +02:00
Codex 4a8eb76a35 Accept compatible initial AV1 sequence headers from video packets
Allow an in-band sequence header to replace provisional codec configuration before frame acceptance. Preserve the admitted output geometry and color, and reject later sequence changes. This fixes AV1 MP4 previews whose av1C coding tools differ from the first packet.
2026-10-16 21:48:00 +02:00
LibretroAdmin 325874cebd Load Content: list previewable movies, keep directory support per core
The installed cores' combined extension list leaves out "/", a core's
directory support: it is a filter for files, and with it in Load Content
offered Use This Directory in every directory while no core was running.
The per-core lists keep it.

Load Content lists the WebM and MP4 files the menu previews with the
in-tree demuxers, without FFmpeg or MPV.

The load_content_filter lane installs a core loading directories and
checks for both.
2026-10-16 21:40:32 +02:00
Codex 10b706f560 Preserve still images and filters across surface replacements
Retain threaded still payloads when an update is refused and reload them.
Track installed filters separately so requests changed in flight settle
with the requested sampler. Reload mipmapped replacements to rebuild all
levels instead of leaving stale lower levels.

Cover refusal, load/post failures, cancellation and in-flight filter
changes. Real GL2/GL3 minification passes on Windows and Linux; baseline
controls reproduce stale white mip levels after gray replacements.
2026-10-16 21:04:42 +02:00
Codex da5d14d2c5 Preserve D3D12 HDR UI precision in a separate FP16 target
Keep HDR UI in a persistent FP16 render target independent of the content
and shader output target. A normalized content target clipped 1000-nit
FP16 surfaces near the 203-nit menu white in HDR10 mode.

Add an opt-in native D3D11/D3D12 harness lane checking actual surface/UI
bindings and HDR capture in HDR10 and scRGB, direct and threaded. Native
RTX 5090 runs pass all eight combinations. Capture remains test-only;
automatic HDR reader admission and visual panel acceptance stay gated.
2026-10-16 20:35:49 +02:00
Codex 2184539a11 Test HDR UI pixel shaders through native D3D rasterization
Exercise the shared sprite and HDR composite pixel entries through a real FP16 render target, alongside direct HDR10 and scRGB output. Check independent linear-light references and alpha across brightness and gamut modes.

Keep this offscreen raster evidence separate from full backend bindings, blending and physical display acceptance.
2026-10-16 20:20:11 +02:00
Codex b28ccdab87 Preserve BT.2020 gamut through HDR UI layers
Signed gamma carries negative Rec.709 channels through FP16 UI encoding and HDR composite decoding in the shared D3D11/D3D12 and Vulkan shaders. Valid BT.2020 colours retain their gamut and absolute luminance.

Add independent offscreen colour checks across gamut settings, menu brightness and HDR10/scRGB output. Native D3D uses an actual FP16 intermediate; Vulkan extracts production math and quantizes UI values to half floats. Old shader math fails both oracles. Physical HDR display acceptance and automatic HDR reader admission remain pending.
2026-10-16 20:07:28 +02:00
LibretroAdmin b9731db602 Load Content: size the extension filter from its sources
The filter general_push() builds is sized from what goes into it: the
running core's and the browser's extension lists, every installed
core's extensions and the manual-scan list, plus room for the fixed
archive, audio mixer, media player and image viewer lists. With the
full core set installed the cores' extensions alone fill 2 KiB, so a
fixed buffer dropped the media player's and image viewer's extensions
and an arbitrary share of the cores' own.

samples/menu/load_content_filter installs 40 cores claiming 600
extensions and checks Load Content lists a file of each and an image.
2026-10-16 19:50:52 +02:00
Codex e326e9b99f Negotiate GPU video import destination formats
Separate NV12/P010 source formats from requested RGBA8 or linear FP16 destinations. Keep zero as the legacy RGBA8 default, allocate the selected format in D3D11/D3D12/Vulkan, and refuse unsupported requests or mismatched existing textures.

Preview requests follow source HDR status and renderer scRGB support. Cover preview selection, threaded descriptor transport, native D3D11 destination creation and invalid destination refusal. HDR reader admission remains gated pending presentation validation.
2026-10-16 19:34:28 +02:00
Codex b7fac8a398 Support linear scRGB destinations in GPU video conversion
Keep PQ/HLG luminance and BT.2020 gamut values in FP16 import destinations across D3D11, D3D12 and Vulkan. Preserve the existing RGBA8 tone mapping path and convert SDR input to linear light for FP16.

Check NV12/P010 output against independent double-precision references on native D3D and both Vulkan import paths. Automatic destination allocation remains RGBA8; HDR reader admission stays gated pending negotiation and presentation validation.
2026-10-16 19:04:56 +02:00
Codex b4637eba45 Convert imported PQ and HLG video to SDR on the GPU
Share transfer, Hable tone mapping and BT.2020 gamut conversion across D3D11, D3D12 and Vulkan imports. Retain SDR behavior and reject unsupported color descriptions. Keep automatic HDR decoder admission gated until FP16 output is available.

Exercise NV12 and P010 imports against HDR color references, including native D3D12 queue and cropped array paths.
2026-10-16 18:51:54 +02:00
Mats 35dd688bb0 steam: default to sdl3, not just sdl2 (#19855) 2026-10-16 18:44:25 +02:00
Codex 3d92516951 Retain container mastering display and content light metadata
Parse MP4 mdcv/clli and Matroska mastering values into fixed-point metadata with explicit presence bits. Ignore invalid optional metadata and carry valid values through reader leases and GPU imports without admitting unsupported HDR conversion.
2026-10-16 18:32:01 +02:00
LibretroAdmin 5b3bb61647 CI: let setup-msys2 run the cross-libc system upgrade
An msys2-runtime update makes pacman kill every MSYS2 process, which
ends the run step's own shell and fails the job. setup-msys2 runs the
upgrade from outside MSYS2 and finishes it after the kill, so the
cross-libc vfs job now asks it for the upgrade and its install loop
only installs packages.
2026-10-16 18:25:13 +02:00
Codex 13c80c8d1f Carry source color metadata through GPU frame imports
Retain MP4 primaries and HEVC VUI fallback, and pass source color descriptions and available WebM content light levels through frame leases, preview surfaces and movie handoff. Keep PQ/HLG hardware admission gated until GPU conversion is supported.
2026-10-16 18:20:18 +02:00
LibretroAdmin 1153287b7d CI: back off and serialize pacman downloads on retry
MSYS2 mirrors answer bursts from shared runners with HTTP 429, which
fails the whole pacman transaction. The install loops in the MSYS2,
Cross-libc-vfs-samples and vulkan-validation workflows now retry up to
five times, waiting 30 s longer each time, and switch pacman to one
download at a time after the first failure. Packages already fetched
stay in the cache, so a retry only fetches what is left.
2026-10-16 18:09:20 +02:00
Codex 29aeac8f51 D3D9 HLSL: stop uploads and readback when locking fails
Check vertex, menu, texture upload and readback locks before using their
mapped memory. Commit vertex dimensions after upload so a failed lock
is retried, and release newly allocated textures or readback surfaces
on failure. Successful API call counts remain unchanged.

Add a portable production-code regression for failed locks, vertex retry,
16/32-bit menu uploads, texture cleanup and readback. Run it with Linux
sanitizers and MinGW under Wine in the graphics workflow.
2026-10-16 18:05:25 +02:00
Codex d40558b0a9 Defer WebM reader canvases until software output
Allocate a private RGB canvas only when a displayed software frame has no caller output. Hardware VP9 frames return a lease token without a canvas; skipped frames retain decoder state without allocating one.

Cover VP8/VP9 allocation failure, rewind, caller output ownership and GPU token lifetime alongside the existing HDR lanes.
2026-10-16 17:57:33 +02:00
Codex 3a98c00eb9 D3D10/11: guard mapped writes and swapchain rebuild failures
Write and unmap D3D10 staging/rotation buffers and D3D11 HDR constants
only after a successful Map. Stop failed swapchain or back-buffer setup
before drawing, and request the existing video recovery path. Commit
D3D11 HDR resize state only after ResizeBuffers succeeds.

Keep successful API call counts unchanged. Add portable failure-injection
coverage of production code for maps, resize, buffer/view creation,
cleanup and HDR/non-HDR resize state, with Linux sanitizer and MinGW runs.
2026-10-16 17:24:25 +02:00
libretroadmin 871aaa716e iOS: stop KSCrash rebinding __cxa_throw
KSCrash routes every C++ throw through its own __cxa_throw, and on iOS
the exception then fails to reach the core's own catch: AppleWin's
RegLoadValue, which catches a missing registry key, aborts instead,
and dinothawr and NooDS abort from retro_load_game the same way.
Uncaught exceptions are still reported, without the throw-site trace.
2026-10-16 17:11:50 +02:00
libretroadmin 5c15216d40 gfx_widgets: turning them off does not wait on the video thread
Under the wrapper the widgets turned off tear down once the video
thread has run a fence queued behind the frame in flight: frames after
the worker is taken away draw none, and the teardown, on the main
thread as before, runs from the runloop once the fence has passed. An
init asked for in between waits for it and is retried after. A deinit
that has waited out the frame itself runs a pending teardown first.
main_exit() no longer waits to clear a flag only it reads.

threaded_video's threaded-off lane turns the widgets off, and off and
straight back on, while a frame draws them: they must not go under it,
the toggle must not wait for it, and they must come back.
2026-10-16 17:11:45 +02:00
Codex a38a8e4880 D3D12: retain resources owned by cached shader descriptors
Keep a reference to each cached SRV resource until the descriptor is
replaced or the shader chain is freed. This keeps resource identity
stable across texture recreation while retaining descriptor caching.

Add a portable production-code regression for address reuse, unchanged
bindings, format and mip changes, null descriptors and chain teardown.
Run it with Linux sanitizers and MinGW under Wine in the gfx workflow.
2026-10-16 17:07:25 +02:00
Tatsuya79 9a2f3c74cb Update ui_qt_widgets.cpp (#19848)
add a couple of new settings
2026-10-16 16:46:55 +02:00
LibretroAdmin 2c896dad75 D3D12: a removed device is reported from its fence
A fence on a removed device reads as complete at the top of its range,
which every frame's wait on the previous frame already reads. That
reading now reports the removal, once, with the device's own reason,
and hands it to the runloop's video rebuild, so a core without a
shader preset recovers too; the refused pass-constant map reports
through the same path.
2026-10-16 16:37:54 +02:00
LibretroAdmin a2df4a2d81 D3D12: a pass constant buffer that refuses to map is left as it was
The slang pass constants are written through a Map whose result was
never read; a buffer that refuses - the device removed, or its video
memory not resident across a display-mode switch - handed a NULL
mapping to the uniform copy. The refusal is now reported once with the
device's removed reason, the pass keeps its previous constants for the
frame, and a removed device goes to the runloop's video rebuild.
2026-10-16 16:28:52 +02:00
libretroadmin e27735f5fd record: starting and stopping a recording does not wait on the video thread
Under the wrapper the GPU recording's buffers are named in the frames
that read them back and retired, not freed, when the recording stops;
the drivers read the recording flag only at their init. Neither end
had anything to wait for.

threaded_video's record lane stops a recording while a frame that reads
it back is drawing, and fails if the stop waits for it.
2026-10-16 06:43:11 +02:00
libretroadmin 819d54f9a3 ozone: a theme switch does not wait on the video thread
Every theme's surfaces are made with the menu's context, so a switch
only sends the new theme its images, through the surfaces, and the
slots the video thread draws from never change under it. Loaded is
the theme's own flag now, not a filled slot. gfx_surface_still() no
longer rewrites a filter that is already set.

tools/wait_budget.sh counts the waits on the video thread per file
against tools/wait_budget.ref, a ceiling that only comes down, in the
menu harness job.
2026-10-16 06:43:07 +02:00
LibretroAdmin 83edb80f02 GL hardware ring: plain swap of the slot sync without pointer atomics
The volatile atomics backend has no pointer operations; on its targets,
single-core ones, the slot sync is swapped as a plain pointer.
2026-10-16 05:53:31 +02:00
LibretroAdmin 957a5a977c GL hardware ring: hand each slot's sync to the video thread by exchange
The core's thread fences a slot and the video thread waits and deletes
that fence. With the presenter behind, the core can reach the slot
again while the video thread is presenting it; exchanging the pointer
makes each sync waited and deleted by exactly one thread.
2026-10-16 05:44:07 +02:00
libretroadmin 89c6ac8f3c retroarch: the widget and menu teardowns wait on the wrapper, not the setting
retroarch_deinit_drivers(), the OSD notification toggle, main_exit()
and the menu driver switch waited out the frame in flight only while
the threaded setting was on, which turning threaded video off clears
before its reinit. video_thread_wait_idle() already does nothing
without the wrapper, so they call it plainly, as driver_uninit() does.

threaded_video's threaded-off lane covers the notification toggle too,
with a frame drawing the widgets as they are switched off in that
window.
2026-10-16 05:31:05 +02:00
libretroadmin cb95aac128 Build the PipeWire drivers in the C89 lane
The PipeWire audio, microphone and camera drivers use positional
initializers for their event tables, plain loops in place of compound
literals and SPA_FOR_EACH_ELEMENT_VAR, and declarations at block start,
so they compile as C89. Under C89_BUILD the SPA and PipeWire include
directories are read as system headers and given __inline__, since
those headers are C99 and outside our control.

The linux-c89 job installs libpipewire-0.3-dev, so configure enables
the PipeWire drivers there and every one of their sources is held to
-std=c89 in CI.
2026-10-16 04:51:53 +02:00
libretroadmin a36004c08b driver_uninit: wait out the video thread whenever the wrapper is up
The threaded video setting's target is the video state's own flag, so
turning threaded video off clears it before the reinit runs. The wait
for the frame in flight went with it, and gfx_widgets_deinit() took the
widgets' fonts from under a frame still drawing them. The wrapper being
up decides now.

threaded_video's threaded-off lane holds a frame drawing the widgets
while the setting goes off, and fails if they go before it is done.
2026-10-16 03:39:37 +02:00
Codex ddaa6abdf9 Lease decoded H.264 GPU surfaces to libretro cores
Add a version-3 decoder callback that exports an independent GPU frame with explicit API and timeline ownership. Preserve version-1 and version-2 allocation boundaries and CPU materialization fallback.

Exercise GPU lease ownership, busy retries and legacy ABI canaries in the interface harness and real D3D11 decoder fixtures.
2026-10-16 03:37:51 +02:00
libretroadmin db5e5dba5d overlay_apng_on_screen_test: judge the order only between close grabs
A grab that comes more than a frame after the last may have missed a
frame, so the change it sees is not taken as out of order.
2026-10-16 02:41:35 +02:00
libretroadmin c60f70b7c6 samples: the Wayland output tests' events are sent on the server thread
libwayland-server is not thread-safe. The KWin and wlroots output tests
sent a mode's removal from the main thread while the compositor thread
dispatched and flushed the same client, which ThreadSanitizer reports
as a race on the connection buffer. The test hands such an event to the
compositor thread and waits for it, and the answer it sets for the
server is set and read under the lock.
2026-10-16 02:41:31 +02:00
libretroadmin 7308438299 content_load_test: wait for the core copy by a deadline
The run-ahead lanes poll the threaded copy of the core until it
answers, for up to 10 s, rather than for 40 one-millisecond polls a
loaded runner can outlast.
2026-10-16 02:41:28 +02:00
libretroadmin eaa3eaea2e video_thread: shader parameters, filter swaps and headset poses without waiting
A shader parameter's value is one atomic word
(video_shader_param_get()/_set()): the frontend sets it on the
driver's own shader and the video thread reads it for its next frame,
with no copy, no queue and no round trip. slang's uniforms name the
parameter, read at the upload, rather than pointing at its value.

Each frame carries the software filter it was staged with, so a filter
swapped in the menu no longer waits the video thread idle: the old
filter and its buffer ride the next frame to the video thread, which
frees them once every frame naming them is drawn.

The headset is sampled on the drawing thread after each frame it
draws, for the core's next, past the frames still queued, and
published whole; the runloop and the core read the newest without
waiting on it. The eyes a core was handed travel with its frame and
are given to the host before that frame is drawn. Without the wrapper
the main thread samples as before. A core's content going and its
resume after a reinit are queued for the video thread
(video_thread_run_queued()) rather than waited for. A core's content going and its
resume after a reinit are queued for the video thread
(video_thread_run_queued()) rather than waited for.

threaded_video_test checks that a parameter write and a pose read do
not wait for a parked video thread, and that the eyes are given on it;
video_filter_menu_test, now also run under the wrapper in CI, holds a
frame on the video thread across a filter swap; openxr_core_test
checks give() and the prediction ahead.
2026-10-16 02:41:24 +02:00
Codex f8465dd3cd Defer MP4 reader canvases until software rendering
Skip the private XRGB allocation for GPU frames, skipped pictures and caller-owned output. Allocate it on first software render while retaining image-size checks at open. Exercise HDR caller buffers, allocation failure/retry, detach and rewind ownership.
2026-10-16 02:37:28 +02:00
Codex bb0c1e7423 Allocate movie core CPU framebuffers only for software output
Avoid allocating the core XRGB canvas at load when a hardware sink is installed. Create and bind it on software fallback, including native seeks and MP4 state restore, with checked allocation size. Verify hardware playback leaves it absent and fallback still produces software pixels.
2026-10-16 02:13:04 +02:00
Codex dc757b9a35 Preserve WebM hardware audio timing after state restore
Advance the restored playback clock once and retain the saved audio position when hardware startup is deferred. Exercise AV1 Opus and Vorbis soundtracks, including exact PCM after live and cold-start restoration.
2026-10-16 02:04:59 +02:00
Codex 1bcf062d8c Defer H.264 CPU readback until a core requests its planes
Extend the experimental decode interface with versioned deferred output and explicit materialization. Keep the v1 allocation boundary and eager callbacks compatible, preserve pending output across busy retries, and retain eager hardware decoding when no spare slot is available.
2026-10-16 02:02:54 +02:00
Codex 5b1d6aedce Play AV1 WebM through the builtin hardware movie path
Reuse WebM GPU pacing, seeking and context restart without a CPU framebuffer. Filter hidden AV1 packets from the display timeline and preserve deferred state restoration. Cover both sequence-header forms, WebM-only builds and native Vulkan movie output.
2026-10-16 01:54:57 +02:00
LibretroAdmin 28d304414e Vulkan: rebuild the swapchain once on a suboptimal present
A suboptimal present is the WSI asking for a new swapchain. On Wayland
the compositor's dma-buf feedback has changed under it: a window gone
fullscreen is offered the scanout tranche, and images allocated before
that for compositing - on NVIDIA, compressed block-linear - are not in
it, so the window is never put on the display plane. Only a rebuild
lets the WSI allocate from the new tranche (#19841, the modifier the
primary plane rejects).

One rebuild per answer: the swapchain built in its place is kept while
it keeps presenting suboptimal (Android without prerotate), and an
optimal present arms the rebuild again. context.flags widens to 16
bits for the new bit, placed before swap_interval to keep the struct
sorted by alignment.

The vulkan_validation lane drives a suboptimal present through the
real present path and checks the rebuild, the hold and the re-arm,
with the tracker confirming nothing the present still needed was
destroyed. Its Makefile takes the tree's Vulkan headers ahead of the
system's, which vulkan_video_caps.h already requires.
2026-10-16 01:29:43 +02:00
Codex 775fba2bc9 Add AV1 WebM hardware reading and GPU previews
Borrow WebM packets into the AV1 hardware session without a CPU canvas. Accept validated sequences from CodecPrivate or the first video packet, preserve pending input across retries, and bound hidden-frame progress.

Route hardware candidates through the existing GPU preview open. Cover reader and inline/threaded preview ownership with portable tests, and add native Vulkan plane/RGBA oracles for WebM, discard/resume and retained leases.
2026-10-16 01:29:13 +02:00
LibretroAdmin 94723929c4 menu_screensaver: keep the snow drift and fall steps signed
menu_ss_rand() returns uint32_t, so the snow effect's random drift and
fall terms are taken modulo first and then offset as int before the
float conversion. Flakes drift left on average and wrap back in from
the right and the top as intended, instead of every step saturating
to the positive clamp and carrying the whole field off the right edge.
Fixes #19801.

samples/menu/screensaver_snow compiles menu_screensaver.c whole with
the font and draw calls stubbed and runs the effect for an hour of
60 Hz frames, requiring at least half the flakes on screen throughout
and a leftward average drift.
2026-10-16 01:08:10 +02:00
Codex 91c69cf647 Enable hardware-only AV1 MP4 thumbnail previews
Add explicit GPU-capable progressive stream and preview opens for the File Browser, keeping software-only image and companion consumers unchanged. Preserve borrowed input and GPU leases without a CPU pixel canvas.

Cover the real preview feeder and player with inline/threaded BUSY, loops, partial headers, unavailable hardware and retained-frame cleanup. Move the preview probe buffer off the stack to stay within the frame budget.
2026-10-16 00:58:04 +02:00
Codex d57336c93b Route AV1 MP4 playback through the builtin hardware core
Admit hardware-only AV1 MP4 content without a CPU framebuffer and defer backend allocation until video hardware startup. Retain the last displayed GPU lease through progress and EOF, and bound hidden-frame progress without treating it as decoder backpressure.

Exercise the real core with a portable mock host and native Vulkan raw and shader-output oracles, including pacing, BUSY, context restart and retained leases after unload. Linux/Wine player, common, formats and Vulkan regressions pass, as do threaded/threadless frontend links and Linux/Win32/Win64 C89 stack checks.
2026-10-16 00:30:22 +02:00
Rob LoachandLibretroAdmin 75f278e286 mcp: WATCH a step and report what follows (#19834)
* WATCH: a step and the look that follows it, in one request

A field run's steps were a press and then a read for each thing it
needed to know: where the player was, where the enemies were, which
mode the game was in. Four requests to take one step, and the reads
were the bulk of a hundred calls for one level.

WATCH <label> <address> watches a byte, and RUN_FRAMES and PRESS carry
every watch along with their own answer, as a line of label, address
and value. A step needs no reads after it.

The lines land where the rows machinery already looks: both commands
answer with a line that opens with their own name, which the row reader
skips as a header, so the watches are the rows and the two commands
declare the shape they now answer in. A failure stays its reason and
carries no reading.

Each watch is resolved when it is set, as RUN_UNTIL resolves the byte
it waits on, so an address nothing maps is refused there rather than
read later. Content closing drops them all, which is what invalidates
where they point. Eight at once; a label already watched keeps its
place in the list when its address changes, so rows stay where a client
saw them.

RUN_UNTIL is left alone. It answers with the byte it waited on in three
columns of its own, and giving it watch rows would collide with those -
its format landed days ago, and changing it is a separate decision.

Also the test PRESS never had: it lets go only of what it pressed
itself, so a button held with SET_INPUT survives a press of the same
button. That was fixed by eye on the branch before #19815 landed and
has been unguarded since; GET_INPUT makes it three lines.

tools/list is 24715 bytes at 49 tools, from 22944 at 48. A tool's own
rows schema is about 450 bytes and this adds three of them, which is
most of the difference; the budget in the end-to-end test records what
each phase has cost it.

* WATCH: keep RUN_UNTIL rows, range-check addresses

* WATCH takes pairs; RUN_UNTIL carries watches

* Memory commands: check bytes and addresses first

* MCP: MENU, DISK and REINIT, three tools for 12

* SET_OPTION: drop streaming's unused event

* stdio_e2e: cap each tool, lower the total

* MCP: STATE and REPLAY tools; files are commands

* Review fixes: bounds, ERANGE, live watch lookups

* STATE: answer as STATE, not the command it runs

* MCP: family tools answer under their own names

* RUN_UNTIL: look its byte up every frame, as WATCH does

RUN_UNTIL keeps the system address it waits on and reads the byte
through the memory map each frame, so a core that moves its memory is
followed, and one that stops mapping the address ends the wait with an
error. VALUE carries the byte as last read. The step reply is clamped
to its buffer, and the deferred reply's family sits ahead of its
generation counter.

command_frames covers a remapped and an unmapped byte under RUN_UNTIL
and WATCH and the watches dropping when content closes; mcp_server
pins the structured RUN_UNTIL and step replies against the tables
command.h ships.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-16 00:27:30 +02:00
Codex b2b79f644f Add an AV1 MP4 hardware reader
Admit AV1 through an explicit hardware-candidate open, borrow samples across nonblocking retries and omit the CPU pixel canvas. Preserve renderable skip semantics while adding discard without AV1 export. Validate container color metadata and retain GPU leases across rewind and close.

Cover partial input, backpressure, discard, retirement and unsupported admission with portable mocks and native Vulkan raw/RGBA MP4 oracles.
2026-10-16 00:13:27 +02:00
LibretroAdmin 9fe4489904 setting_list: shrink rarch_setting_t to 80 bytes
The option-list / extension-filter string shares the aux union with
the float rounding string, since no row type uses both. index_offset
and size are 16 bits wide, and the never-read index byte and directory
empty-value string are gone, together with the arguments that only
fed them. rarch_setting_t is 80 bytes on 64-bit targets and 56 on
32-bit ones.
2026-10-16 00:01:21 +02:00
LibretroAdmin 9628c86f07 Vulkan: prefer B8G8R8A8_UNORM for the 8-bit swapchain
The 8-bit SDR swapchain takes B8G8R8A8_UNORM in the sRGB colour space
whenever the surface lists it, and the first 8-bit UNORM format only
when it does not. B8G8R8A8 maps to DRM XRGB8888, which every primary
plane scans out, so a compositor can give a fullscreen window direct
scanout regardless of the order the driver lists its formats in (the
NVIDIA Wayland WSI lists R8G8B8A8 first, which mutter cannot put on
the plane). See #19841.

The choice lives in gfx/common/vulkan_surface_format.h; the
samples/gfx/vulkan_surface_format lane checks it against RGBA-first,
BGRA-first, wide-first and BGRA-less format lists.
2026-10-15 23:57:25 +02:00
LibretroAdmin 53d4eafb0d input: turbo and hold in their own function, with a harness
input_turbo_hold_apply() takes a RetroPad button's pressed state and
returns it with hold and turbo applied, for every turbo mode;
input_state_device() calls it for buttons up to R3.

samples/input/turbo_hold drives it frame by frame: one toggle per
hotkey press however long it is held, the duty-cycle pulse, release in
hold mode, classic and classic-toggle edges, the D-pad exclusion and
hold latching.
2026-10-15 23:55:20 +02:00
Codex 2eaa271d0f Dispatch AV1 hardware packets without copying input
Add a caller-owned packet stream around the AV1 session and retained backend. Yield per OBU, preserve decode/export BUSY cursors, open in-band sequences, and preserve exported leases through retirement. Exercise packet prefixes, hidden/replayed frames, discard/cancel/error and raw/RGBA Vulkan output.
2026-10-15 23:55:16 +02:00
Anthony SchemelandClaude Opus 5.5 92d9c02637 libretro-common/Makefile.test: link test_rwebm_audio with -lm (#19835)
rwebm's audio decode calls exp(), but the test linked only
$(LIBCHECK_LIBS). Ubuntu's pkg-config line for check carries -lm, so
CI links; where it is bare -lcheck (openSUSE), the link fails with
"undefined reference to symbol 'exp'" and unit-tests stops there.
Give it its own _LIBS variable, as test_rpng has for -lz.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-15 23:47:13 +02:00
LibretroAdmin 77b40416ba gx_joypad: bound the pad name by the pads it tracks
gx_joypad_name indexes pad_type, which holds DEFAULT_MAX_PADS entries,
with whatever joypad index the frontend passes; any user's device index
reaches it through joypad_driver_name and input_driver_autoconfigure_pad.
It now returns no name past the driver's pads, as gekko_joypad_name does.

The gx_pad_sticks lane asks query_pad and name for every index from
DEFAULT_MAX_PADS to MAX_USERS, and its sanitizer build no longer
recovers, so an out-of-bounds read fails the run.
2026-10-15 23:42:14 +02:00
Anthony SchemelandClaude Opus 5.5 8eb1f844e0 gx_joypad: bound query_pad by the pads it tracks
gx_joypad_query_pad tested pad < MAX_USERS (16), but pad_type has
DEFAULT_MAX_PADS entries (4 on GEKKO), so pads 4 to 15 read past it.
Every other pad_type access in the file is bounded by DEFAULT_MAX_PADS.

Shown on x86 only, with the file built against libogc v3.1.0 and
tools/platform_stubs/gekko under UBSan: query_pad(4) is an index out
of bounds before, and clean after. Not built with devkitPPC.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 23:42:13 +02:00
LibretroAdmin fec890457f gl2: define the ARB_sync wait results when the headers lack them
The PowerPC macOS SDK headers carry no ARB_sync names; the PBO
readback's fence check needs GL_ALREADY_SIGNALED and
GL_CONDITION_SATISFIED alongside the ones the hardware ring already
falls back on.
2026-10-15 23:40:40 +02:00
Codex 615edd2d00 Accept bounded unsized AV1 hardware input
Decode container-final OBUs directly without inserting a length field. Preserve borrowed-input retries and packet-relative Vulkan tile offsets. Cover truncated input, replay and unsized raw/RGBA hardware oracles.
2026-10-15 23:39:04 +02:00
LibretroAdmin 4eb537eff4 netplay: no bit for a slave's own input, bound core input queries
Real-client masks are built by one helper that skips the slave's own
input, filed under MAX_CLIENTS, so no shift reaches 32. The remaining
client-number masks shift unsigned.

A core's input query reads only the words its device's state carries:
analog sticks by index 0..1 and axis 0..1, analog buttons as their
digital bit at full pressure, joypad and pointer bits below 32, and
keyboard words below the state's size; anything else reads 0.

samples/network/netplay_input covers both helpers.
2026-10-15 23:35:18 +02:00
Anthony Schemel 6dd959ea8d input, netplay: shift unsigned where the bit can be 31
1 << n on an int is undefined when n is 31. Netplay's loops over
MAX_CLIENTS (32) and its input-share loops over 32 bits reach it; the
masks they build are uint32_t, so its variable shifts become 1U << n.

turbo_pressed kept its 'turbo held' flag in the sign bit of an int32_t,
set with 1 << 31 and tested with >= 0. It is now uint32_t, the flag is
1U << 31, and the test reads that bit.

Closes #19837
2026-10-15 23:35:17 +02:00
Codex 8e11fa0326 Decode AV1 Main 8/10 on Vulkan Video
Register a HAVE_RAV1 backend with capability-based extension and queue selection. Reuse nonblocking decode/transfer timelines and leased NV12/P010 outputs, retaining sequence parameter storage through decoder retirement.

Add native raw and RGBA shader oracles covering 16 streams, accepted-input overwrite, replay, backpressure, unsupported opens and output leases after close. Grain, HDR, size changes and builtin container/player routing remain unsupported.
2026-10-15 23:30:34 +02:00
LibretroAdmin 631c443892 core options, runahead: one allocation for value hashes, flat input list
Option value hashes live in one pool per manager, indexed by value,
and every value and label append is checked, so a failed append ends
the parse without writing outside a list. The legacy parser reads the
description and values in place from the variable string.

Runahead's input logging keeps the tuples the dense cache has no slot
for in one flat array; each tuple's ids are sized on first write.

samples/core_options drives the manager with each append failing in
turn; samples/runahead covers the input list, including each realloc
failing in turn.
2026-10-15 22:55:31 +02:00
Anthony Schemel c88fc9b66e core options, runahead: check three small allocations
core_option_manager_parse_variable and _parse_option wrote each value's
hash through an unchecked malloc; a failure now fails that option, as
the functions' other failures do. runahead_input_state_set_last read
input_state_list->size after mylist_create could leave it NULL; it now
returns. Not reproduced: needs a failing allocator.

Closes #19838
2026-10-15 22:55:31 +02:00
Codex 81a5ff5f1e Map AV1 hardware pictures to Vulkan StdVideo parameters
Translate sequence, frame tools, reference metadata, grain and borrowed tile spans into stable caller-owned Vulkan parameter bundles. Preserve UV delta and loop-filter update syntax required by StdVideo.

Validate parameter units and tile offsets against 12 real streams and add native session setup checks for Main 8/10-bit normal, still and grain sequences. Decode submission and playback routing remain separate work.
2026-10-15 22:51:55 +02:00
Codex 4f79215432 Add Vulkan AV1 decode declarations and capability probe
Import the AV1 StdVideo and decode extension declarations from Vulkan-Headers v1.4.328. Query Main 8/10-bit capabilities with and without film grain before implementing the device adapter, and document native RTX 5090 limits without advertising AV1 playback.
2026-10-15 22:34:46 +02:00
Codex cda8c484ff Add AV1 hardware submission session
Connect header parsing and reference transactions to borrowed backend submission, BUSY retry and independent NV12/P010 output leases. Snapshot DPB metadata and order hints; replay stored frames without decoding them again.

Exercise slot rotation, cancellation, failure rollback, grain propagation and lease cleanup with a mock backend. Run 180 real-stream transactions with 145 accepted decodes and 35 replays under injected submission/output backpressure. Concrete device adapters and player routing remain separate.
2026-10-15 22:29:19 +02:00
Codex afac02926b Validate and commit AV1 reference transactions
Keep slot identity, showability and geometry tentative until frame submission is accepted. Validate replay eligibility, frame-ID progression and reference scaling; reject stale tickets and consume key-frame aliases without owning GPU surfaces.

Use the helper in the native header/tile harness. Compare 180 real transactions and 35 replays with an independent slot model, reject corrupted replay selectors and cover reset, rollback, scaling and epoch boundaries.
2026-10-15 22:14:41 +02:00
Codex dd25947632 Keep DXGI presentation sublabel within C89 limits
Retain the Smooth Motion presentation-path warning while keeping the English literal under the 509-character limit checked by the MSVC6 compatibility build.
2026-10-15 22:04:01 +02:00
Codex d9c5a8ad5f Parse AV1 film grain and locate borrowed tiles
Validate film-grain points, coefficients and selected-reference inheritance while preserving immutable metadata and failure cursors. Extend the native header chain through grain and byte alignment into borrowed tile groups.

Compare 140 real 8/10-bit coded headers, ten inherited grain blocks and 182 tile hashes against FFmpeg traces and independent packet slicing. Cover malformed points, coefficient bounds, inferred syntax, aliases and truncation synthetically.
2026-10-15 22:04:01 +02:00
Craig 2958b3716b openxr: retry refresh rate, add supported headsets on quest, fix washed out picture in immersive (#19844)
* openxr: allow GLES 3.1/3.2 contexts, add supported headsets on quest

* openxr: fix washed out picture in immersive mode
2026-10-15 22:01:04 +02:00
Codex 94f4b023c7 Parse AV1 skip and global motion headers
Derive skip-mode pairs and parse compound, warped-motion and global model parameters through the film-grain boundary. Keep primary predictors immutable and preserve cursors on malformed or truncated input.

Cover model extrema, predictor precision, recentering and skip eligibility synthetically. Extend the real-stream chain to compare motion flags, identity models and end positions against FFmpeg traces.
2026-10-15 21:53:09 +02:00
LibretroAdmin f81dcb4316 intl: drop the Bright theme rows from the packed translations
MENU_ENUM_LABEL_VALUE_OZONE_COLOR_THEME_BRIGHT no longer exists, so its
rows leave the ca, de, es, fr, ga, sk, sv and vn packed tables. Each row
goes from the blob, its initializer and ids[] together, and the contiguity
constant shrinks by the row's size, so every header still pairs row for
row under tools/msg_hash_packed_check.c.
2026-10-15 21:43:24 +02:00
LibretroAdmin ffa14cdf0a menu: sublabel for DXGI Presentation
The setting had no sublabel: the row was missing from the sublabel map.
The text says what the setting does for the glcore and vulkan drivers,
and that with NVIDIA Smooth Motion the menu and cores have to stay on
one presentation path, since the display driver does not survive a
switch of presentation API within one session.
2026-10-15 21:35:31 +02:00
Codex e7c92bdd93 Parse AV1 references and pre-tile frame setup
Resolve explicit and short-signaled reference indices, validate frame-ID deltas and derive reference-sized geometry without mutating committed slots. Parse superres, intrabc and motion flags through the tile-info boundary.

Exercise wrap, tie, fallback and truncation cases. Compare 135 real 8/10-bit frame setups and 85 inherited picture blocks against FFmpeg traces. GPU ownership and successful-decode metadata commit remain with the caller.
2026-10-15 21:34:52 +02:00
Noam Gilor dfc52ea8cf Revert "ozone: add Bright color theme (#17714)" (#19843)
This reverts commit 8bc6529e46.
2026-10-15 21:37:20 +02:00
Codex 66ea372db1 Parse AV1 quantization and picture filter tools
Parse segmentation, delta-Q/LF, lossless flags, loop filters, CDEF, restoration and transform mode into caller-owned metadata. Inherited state remains immutable; failures preserve input cursors.

Validate real all-intra streams through the native header parser chain, plus inherited-state, clipping, alias and truncation cases. Reference ownership, remaining motion syntax and hardware submission remain separate.
2026-10-15 21:04:28 +02:00
Codex a2eb8227c5 Parse AV1 frame prefixes and explicit geometry
Read frame flags, show-existing selectors, IDs, timing and refresh metadata without changing reference state. Parse explicit dimensions, superres scaling and render size into caller storage; failures preserve bit cursors.

Compare real hidden/replayed, error-resilient and still-picture streams against FFmpeg traces. Reference validation and the remaining uncompressed header are separate work.
2026-10-15 20:36:58 +02:00
Codex 146ab1ddb2 Parse AV1 tile layouts from frame-header bits
Derive uniform and nonuniform partitions, MI boundaries and tile-size fields in caller-owned storage. Reject invalid context tile IDs and preserve the input bit cursor when parsing fails.

Validate real 8/10-bit layouts against FFmpeg traces and feed the derived layouts directly to borrowed tile-group iteration, including partial superblocks.
2026-10-15 20:25:55 +02:00
CraigandLibretroAdmin 2c961c0ef6 openxr: add v hotkey to toggle flat, stereoscopic or immersive (#19840)
* openxr: add v hotkey to toggle flat, stereoscopic or immersive

* openxr: Headset View only narrows a presenting headset

The Headset View hotkey and its status filter apply only while a
headset presents, so a desktop Stereo Mode keeps its STEREO bit and
the hotkey stays silent without a headset. The bind follows the other
headset hotkeys: always in the bind list, hidden from the menu without
OpenXR, reachable from a headset controller action, and defaulted to
V for player 1 only.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-15 20:22:50 +02:00
Codex fda9d74926 Expose validated AV1 tile groups as borrowed spans
Check tile ranges, byte alignment and little-endian lengths before exposing payloads. Preserve input ownership and iterator cursors without allocating tile arrays or copying compressed data.

Cover malformed groups and syntax limits, plus real 8/10-bit multi-tile streams located using FFmpeg frame-header traces.
2026-10-15 20:12:49 +02:00
Codex b6d590ecb5 Parse AV1 sequence headers and validate configuration agreement
Read timing, operating points, feature flags and color layouts into caller-owned storage. Validate bounded syntax and av1C agreement without copying compressed payloads.

Exercise malformed and truncated headers plus real 8/10/12-bit MP4 and Matroska fixtures against ffprobe.
2026-10-15 20:01:09 +02:00
Codex 7bc8d79488 Apply project formatting to AV1 parsers and tests 2026-10-15 20:01:09 +02:00
LibretroAdmin 7be1e534a9 d3d9_last_pass: stub the window-keep symbols d3d9hlsl.c links against
d3d9hlsl.c now uses win32_window_keep, win32_window_tag,
win32_window_was_taken, win32_window_remake and win32_has_focus; the
sample's stubs.c lacked them and the HLSL test failed to link.
2026-10-15 19:46:10 +02:00
Codex 684d17b597 Recognise AV1 containers and expose borrowed OBU views
Identify av01/av1C MP4 and V_AV1 Matroska tracks while preserving existing codec enum values, borrowed packet storage and timestamps.

Add allocation-free configuration and low-overhead OBU framing helpers with bounded LEB128 lengths, profile/depth/chroma checks and failure-safe cursors. Permit an unsized final sample OBU in both containers; require explicit sizes in configuration OBUs. Sequence/frame payload parsing and AV1 decoding remain separate work.

Test borrowed views, malformed framing and partial-input retries on Linux and Windows. Add sanitizer CI and four generated 8/10-bit MP4/WebM fixtures checked against ffprobe configuration/packet hashes, timestamps and key flags.
2026-10-15 19:38:19 +02:00
LibretroAdmin 5830412ad4 win32/dxgi: keep the Direct3D 12 device across swap chains
The presentation bridge made a Direct3D 12 device and queue for each
swap chain and released them with it. The display driver's layers hang
their own objects for a chain off the device it was made on and clean
them up as the next chain is made; with that device gone, the next
CreateSwapChainForHwnd crashes in the driver's frame-generation layer,
or that layer comes up without it. The device and the queue chains are
made on are now kept for the life of the process, as a game keeps its
device, and replaced only for a chain on another adapter or after the
device is lost.
2026-10-15 19:19:11 +02:00
Codex 469325a792 Decode VP9 Profile 0 and 2 with Vulkan Video
Negotiate the VP9 extension and feature in both device-creation paths, preserving explicit core disable requests. Backport the required VP9 definitions from Vulkan-Headers v1.4.328.

Register a VP9 backend with the existing hardware session and reuse Vulkan NV12/P010 output leases and sampled imports. Preserve effective segmentation/filter state, named-reference aliases and per-slot color metadata. Retain the DPB-to-output copy and capability-based fallback.

Add portable picture and negotiation tests, plus optional native IVF/reference-plane and RGBA comparisons covering show-existing replay, backpressure, retained frames after close and sampled-import fallback.
2026-10-15 19:18:48 +02:00
libretroadmin dcba6b8e85 gfx_widgets: deinit hands the video thread its fonts back, unwaited
Any thread may retire a font: one another thread made goes on that
thread's lock-free stack, which it takes into its own deferred queue
at its next font_driver_free_pending() - freed on its frames, or at
once on the teardown flush. gfx_widgets_deinit() retires the widget
fonts that way instead of a blocking round trip to the video thread,
and leaves the barrier against a frame still drawing to its callers,
as driver_uninit() and the widget toggle already had it;
retroarch_deinit_drivers() now does the same.

font_owner_test hands fonts back from several threads while the video
thread frees, and gfx_widgets_sanitize_test checks that deinit under
the worker frees none of the worker's fonts itself.
2026-10-15 18:57:30 +02:00
Codex 660beed09d Use push constants for sampled Vulkan video imports
Pass the color header directly to the sampled compute shader so sampled imports need no staging buffer or transfer dependency. Allocate copied fallback buffers only for the selected idle slot and retain them for later reuse.

Extend the GPU oracle to 48 cases, checking command selection, zero sampled allocations, fallback failures and return to sampling. Require absent staging buffers in the native HEVC render oracle.
2026-10-15 18:55:17 +02:00
Codex 31eadc0c4e video: retain GL hardware slots until queued readers retire
Use the existing queue-ownership wait before GL slot reuse. A previously signalled GPU fence does not cover a duplicate still pending or drawing, and the short duplicate wait may expire before that reader retires.

Add a portable scripted GL-ring regression for queued, busy and full-ring duplicates, inactive flags and a duplicate targeting another slot. Run it under ASan/UBSan and TSan in graphics sample CI. Preserve the framebuffer ring and zero-copy handoff.
2026-10-15 18:55:04 +02:00
Codex d326d1c9dd Trim staging allocations for sampled Vulkan imports
Allocate only the color header for sampled video frames. Size copied staging to the active NV12/P010 layout and grow idle slots on demand, retaining their capacity for later format switches.

Preserve existing buffers and frame timelines when growth fails. Cover header-sized storage limits, allocation failures, busy slots, retry and steady-state allocation counts.
2026-10-15 18:33:52 +02:00
Codex f10bf50860 video: retain shared hardware slots while duplicates are queued
Resolve duplicate frame entries to the last presented hardware slot when checking whether a slot is still queued or drawing. Duplicates carry no explicit hardware index, so the old predicate allowed texture reuse after the short duplicate wait while the consumer still needed it.

Extend the native D3D9/Ex ring regression with a duplicate whose earlier fence is already signalled. Both device types reproduce early reuse without the correction and remain blocked until retirement with it. Preserve the existing wait protocol and zero-copy handoff.
2026-10-15 18:31:59 +02:00
Codex a85e2b7015 tests: cover D3D9 queued slot reuse and retirement
Exercise production hardware-ring publication against a scripted full frame queue. Keep reuse blocked while the target texture is pending or drawing, even after its completion fence is signalled, then resume on retirement.

Drain the remaining texture identities through the driver handoff and verify cache invalidation, ring/fence destruction and final core texture references on plain D3D9 and Ex devices.
2026-10-15 18:23:56 +02:00
Codex b5db39ab5d Sample Vulkan video planes without import staging copies
Export optional leased Y/UV views from sampleable NV12/P010 decoder outputs. Convert those images directly in the Vulkan compute importer, preserving the buffer path when allocation, views or the sampled pipeline are unavailable.

Forward views through thumbnails and threaded movie handoff. Verify color, copy counts, injected allocation/view failures, native HEVC output and direct/threaded presentation. The decoder DPB-to-output copy remains.
2026-10-15 18:21:22 +02:00
Codex 411826a012 tests: cover contended D3D9 device turns
Run frontend and core workers against the production device-turn protocol on plain D3D9 and Ex devices. Check recursive exclusion, frontend priority, device-state visibility, invalidation and balanced owner/waiter retirement across 32 rounds each.

Use bounded watchdogs and join every worker before releasing test state.
2026-10-15 18:18:28 +02:00
Codex 137b361f16 tests: exercise D3D9 hardware texture and reset lifetimes
Include the HLSL driver's production ownership helpers in the native device fixture. Cover recursive core/frontend turns, retained replacement textures, idempotent context destruction, and 24 reset cycles on plain D3D9 and D3D9Ex devices.

Verify default-pool retirement before plain reset and retained resources across Ex reset. Keep large driver test state out of the stack.
2026-10-15 18:13:56 +02:00
LibretroAdmin 88a9759fec win32/dxgi: keep the DXGI and Direct3D 12 runtimes loaded
The presentation bridge loaded dxgi.dll and d3d12.dll for each swap
chain and freed them with it. In an OpenGL or Vulkan session nothing
else holds them, so the first chain's teardown unloaded the Direct3D
runtime and the display driver's layers under it, which keep
process-wide state; the next chain brought them back on top of what
was left and crashed in the driver's frame-generation layer inside
CreateSwapChainForHwnd, or came up without it. The two runtimes are
now loaded once and stay for the life of the process.
2026-10-15 18:10:23 +02:00
Codex 4118377622 d3d9: enable presentation statistics for flip-model devices
Create flip-model Ex devices with the flag required for swap-chain presentation statistics. Leave discard-mode and legacy creation flags unchanged.

Run the native device fixture through production creation and check its flags. Without this flag, the Windows presentation-count query fails with D3DERR_INVALIDCALL. Use whole-program LTO to discard frontend-only entry points from the included common unit.
2026-10-15 18:08:25 +02:00
Codex 7ed3c38889 openxr: retry refused refresh rate requests
Cache a refresh rate only after runtime acceptance. Retry failed requests at most once per second without repeated failure logs, and reset the deadline when the desired rate changes. Clear request and retry state on session start.

Cover refusal, recovery, throttling, returning to an accepted rate, cancellation, restart and unavailable extension functions.
2026-10-15 18:02:36 +02:00
Codex d2e1d1dd44 openxr: retry failed performance requests per domain
Remember successful CPU and GPU requests independently and retry failures at most once per second. A setting change bypasses the retry deadline, and session start clears applied state. Warn only on the initial failure for each setting.

Cover partial failures, retry throttling, recovery, setting changes and session restart. Have the runtime stand-in explicitly report unavailable velocity in its chained output.
2026-10-15 17:58:23 +02:00
Codex 34ee86fb85 Import shared decoder arrays directly into D3D12
Remove the DPB-to-output copy for compatible D3D12 H.264, HEVC and VP9 playback. Rename physical decode surfaces while readers hold leases, and carry array slices and crops through thumbnail and movie imports. Retain copied output when sharing is unavailable or for D3D11 rendering.

Cover reference remapping, BUSY retry, retained leases, fallback and production NV12/P010 array import.
2026-10-15 17:55:02 +02:00
Codex 7e55989828 gl2: retain readback fences after failed completion queries
Map a readback buffer only after a successful fence completion result. Keep failed queries available for retry or existing slot cleanup. Cover failed queries and successful retries in both recording pixel formats.
2026-10-15 17:51:47 +02:00
Codex c4e5a826f1 eventcount: bound timed spins and validate overrides
Make nonpositive waits balanced epoch polls. Account for monotonic elapsed time during Linux spinning and pass only the exact remaining interval to futex, checking the budget during longer spins. Bound parsed spin overrides before narrowing and avoid inclusive-loop wrap. Add deterministic clock/futex probes and portable polling checks without changing notify ordering.
2026-10-15 17:44:49 +02:00
Codex 8307af405f Skip WebM display exports during movie catch-up
Advance skipped WebM frames without software color conversion or GPU surface export. Keep the movie core's next/skip choice across input and decoder retries so seek catch-up preserves references without copying every intermediate picture.

Cover retry pinning, trailing hidden frames, lacing, lease lifetime and software reconstruction, plus native D3D11/D3D12 post-seek sample comparisons.
2026-10-15 17:20:35 +02:00
LibretroAdmin 6891180528 d3d9_hlsl: the window is kept across a restart, and has a focus hook
With a Direct3D 9 hardware core no input came through - keyboard or
controller - until the window had been switched away from and back.

Two things set d3d9_hlsl apart from d3d11 and d3d12 there.

It had no focus hook. Unthreaded, a driver without one is taken as
focused; the threaded wrapper takes it as never focused. So with
threaded video every controller read as idle (unless "Use Controllers
in Background" is on), and with "Pause when not active" the content
never ran at all. It now has win32_has_focus(), as the other Windows
drivers do: focused when its window is the foreground window.

And it destroyed its window at every restart and made another. Loading
content for a hardware core restarts the driver, so the core always ran
in a new window - one that can come up without the foreground, behind
whatever had it, and then Direct3D's keyboard (DirectInput's foreground
access) and the focus above have nothing until the window is activated
by hand. d3d11 and d3d12 leave the window up for the next driver of
their own kind (win32_window_keep()), so their cores keep the window,
and the focus, the menu had. d3d9_hlsl now does the same: it tags its
window, leaves it up when it is freed - the device and everything made
on it are gone by then - and makes a new one if no device can be made
on the one it took (win32_window_remake()). A restart, a fullscreen
toggle included, no longer destroys and creates a window.

The input driver is not touched: it is the frontend's, started and
kept as before, and simply finds the same window there.
2026-10-15 17:19:54 +02:00
Codex b86190c08e Present VP9 WebM movies through the GPU frame sink
Reuse the hardware WebM stream in the builtin media core, retaining leases across backpressure and catching up seeks asynchronously. Preserve progressive input ownership, reset/restore and renderer restart, with software fallback when a hardware session cannot open. Exclude hidden VP9 packets from the display schedule.

Add portable core ownership regressions and a native D3D11/D3D12 movie-core sample oracle for NV12/P010 fixtures.
2026-10-15 17:04:49 +02:00
Codex ac2ca61c60 record: preserve signed source stride when batching video rows
Keep bottom-up input pitch signed through the video FIFO row packer and stop advancing the source after the final row. Preserve the packed positive-stride bulk copy. Exercise signed packed/padded rows at contiguous and split ring boundaries, including one-row inputs, and encode/decode alternating bottom-up frames with pixel and timestamp checks.
2026-10-15 17:04:33 +02:00
LibretroAdmin 05f74483f0 win32: a window on its way out reaches only DefWindowProc
A kept window a driver does not take is destroyed inside that driver's
window creation, before it has a window, a surface or a context. The
size, move, activation and focus messages the desktop sends a window
as it goes down went through the window procedure as the main window's
and on into the video and input drivers; now every message to a
retiring window (win32_retiring_hwnd) goes straight to DefWindowProc,
which also covers win32_window_remake() and the held video thread's
teardown.

The win32_window_proc harness gains a lane sending a kept Vulkan
window those messages as it is destroyed for a DXGI-presenting driver.
2026-10-15 16:39:35 +02:00
CraigandLibretroAdmin c2b19c4710 Fix black levels on GLES, allow hw ring sync on GLES 3, add headset performance boost (#19839)
* Fix black levels on GLES, allow hw ring sync on GLES 3

* Add performance menu setting for openxr to boost CPU/GPU clocks

* OpenXR: restore sRGB writes after the GLES copy, spec levels for boost

The GLES headset copy turns GL_FRAMEBUFFER_SRGB_EXT off only around its
blits and back on after them, since gl2's sRGB shader passes share the
context, and only when EXT_sRGB_write_control picked the sRGB
swapchain, queried through gl_query_extension.

Headset Performance Boost defaults to off. On asks for CPU boost; off
returns both domains to sustained high, the level a runtime holds when
never asked. openxr_core_test covers the extension's loading and the
levels asked on each change.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-15 16:00:54 +02:00
LibretroAdmin 3d67f63a06 video_thread_hw: Direct3D 9 hardware cores under the threaded wrapper
The ring gives a D3D9 core three sync indices and remembers each
slot's texture by pointer: referencing it is device work, which only
the device's holder may do. The driver takes the texture up inside the
frame. wait_sync_index waits until no queued frame names the slot and
the video thread has issued the one that did, as for Direct3D 11
version 3. Since the slots are not referenced, invalidating the
hardware render cache drains and forgets them, as for Vulkan.
2026-10-15 14:03:48 +02:00
LibretroAdmin 42db0ada59 d3d9_hlsl: libretro_d3d9.h hardware render interface
A core asking for RETRO_HW_CONTEXT_D3D9 gets the d3d9_hlsl driver's
device. Its frame texture is put in place of the first pass's texture
and goes through the shader chain, rotation and scaling as a software
frame does, with no copy; the menu and pause repeat it. The driver's
own drawing state is put back with one state block per frame, and
screenshots read the back buffer rather than whatever render target
the core left bound.

The core and the driver take turns on the device. Unthreaded this is
bookkeeping. Under the threaded wrapper the turn is one atomic word,
a waiting frontend goes first, and a waiter parks on an eventcount;
no lock. The wrapper's ring hands the driver each slot's texture by
pointer through hw_ring_install, and the driver references it inside
the frame, holding the device.

A V-Sync switch resets the device only when the interval really
changes. On a device without Ex the core gets context_destroy before
the reset and context_reset after it; under the wrapper the core can
only be told on its own thread, so there the device keeps its interval
and a lost device is rebuilt from the main thread.

The frontend picks d3d9_hlsl for the context, and it is what
GET_PREFERRED_HW_RENDER reports for that driver.
2026-10-15 14:03:43 +02:00
LibretroAdmin ff92f69bf5 libretro: Direct3D 9 hardware render interface
RETRO_HW_CONTEXT_D3D9 and RETRO_HW_RENDER_INTERFACE_D3D9 existed with
no interface behind them. libretro_d3d9.h defines it.

The core renders on the frontend's device and hands its frame over as
a render target texture of its own, which the frontend samples where
it lies. The device is not thread-safe and a frontend may present from
another thread, so the core uses it only between lock_context and
unlock_context, keeps one texture per sync index and waits on the index
before drawing into one again - the scheme libretro_d3d11.h reached in
version 2, here from the start, so no negotiation is needed: a later
version only appends members.

context_destroy/context_reset cover a reset of a device without Ex,
which loses the default pool, and a new device. The compiler hook is
typed with opaque parameters so that the header needs only <d3d9.h>.
2026-10-15 14:03:39 +02:00
libretroadmin 431cc2c814 content_load_test: the probe-cached lane holds the running core unasked
The running core is never probed: a push of its own path, and a direct
libretro_get_system_info() on it with no record to serve, leave its
retro_set_environment uncalled and keep nothing for it. The record
keeping is exercised on harness_core_nohw.so, held open by the test
while it is not the running core: probed and kept on disk, served from
the record after that, asked again once its file changes, and read
back from disk after the records are dropped.
2026-10-15 13:51:54 +02:00
LibretroAdmin fc5786cbc8 CI: run the VP9 hardware-session samples
rvp9_reference_test, rvp9_hw_session_test and rwebm_hw_stream_test are
self-contained and pass under ASan/UBSan; list them in RUN_TARGETS so
the libretro-common samples job runs them.
2026-10-15 13:51:26 +02:00
libretroadmin 277127dac1 gfx_widgets: lay out on the video thread, without a state lock
Under the threaded wrapper the video thread owns the widgets outright:
it computes the layout from the scale factor carried in the frame
info, rebuilds the widget fonts there, and frees them there at
deinit. The main thread only resets the monitor after a relayout. The
state lock and the wrapper's yield/resume go away; the lock budget
pins the widgets' frame and worker step, and the font driver's
per-thread follow and free, at zero, and gfx_widgets.c joins the
atomic backend lane.
2026-10-15 13:43:16 +02:00
libretroadmin 1bf932eb0d font_driver: fonts belong to the thread that makes them; no file lock
A font belongs to the thread that made it, the main thread or the
threaded video wrapper's: each thread keeps its own list of live
fonts, its own deferred-free queue, aged on its own frame clock - the
main thread's by video_driver_frame(), the video thread's by the
wrapper after each frame it presents and flushed at CMD_FREE - and
follows a language change itself. font_driver_reload_fonts() moves a
language epoch and rebuilds the calling thread's fonts; the video
thread's are rebuilt by font_driver_follow_language() before its next
frame draws. No thread walks or rebuilds another's fonts, so the OSD
font the video thread makes no longer shares a list with the main
thread's, and a font is only ever rebuilt where it is drawn.

The shared file bytes are taken and let go without a lock: entries are
published with a CAS and never removed, and each holds a reference
count that one thread at a time moves through loading or dropping the
bytes; a thread that finds either takes a private copy rather than
waiting. The fallback pkg directory is published the same way.

font_owner_test checks both, under ASan and TSan.
2026-10-15 13:43:12 +02:00
libretroadmin b2538f1249 gfx_widget_load_content_animation: carry the icon directory in the start
A start resolves its icon on the caller's thread, so it takes the
directory the widgets set at init rather than leaving it empty, and
the icon is found again.

samples/gfx/gfx_widget_load_content_animation starts the animation
off the widgets' thread and checks that the owner loads the system's
icon, the 'retroarch' fallback, or none; Linux-samples-gfx runs it
under ASan and TSan.
2026-10-15 04:50:36 +02:00
LibretroAdmin ef2fefbce4 video_thread: take the movie handoff out of video_thread_frame
A frame rarely carries a movie import or clear, so moving one into its
ring slot is a VIDEO_NOINLINE helper; the push keeps one call, and the
helper's early-out covers the frames without one.
2026-10-15 04:39:29 +02:00
libretroadmin f8b7c5de9c gfx_widgets: setters hand their change to the widgets' owner
The widget setters - volume, the libretro, generic and progress
messages, the screenshot and state slot, the achievement appearance,
the load content animation's start and progress - push a command on
the widget's own MPSC stack and return, as the leaderboard display's
already did; the widget's iterate() applies them on the thread that
draws the widgets. Whatever a setter reads beyond its arguments, the
settings or the content and core paths, is read by the caller and
carried in the command. A setter no longer waits for a frame being
drawn under the threaded video wrapper. Task updates ride an
mpsc_stack too, and video_driver_frame() leaves the status text to
the frames it hands the worker, which leaves the state lock to the
layout and the draw.

tools/lock_acquire_budget.sh also counts gfx_widgets_state_lock()
calls, and holds every setter at none.
2026-10-15 04:39:16 +02:00
libretroadmin e6dc82cbbd video_thread_wrapper: hand the video thread over without a lock
The thread that runs a wrapper's loop and is kept across a driver
restart is now driven by one atomic flag word - a job to run, a loop
running, held, quit - and parks on an eventcount: for its next job,
and for the frontend waiting on it to leave a wrapper's loop. The job
is published by the release that sets its flag, and a hold is taken
with a CAS that loses to a quit already set.
2026-10-15 04:39:12 +02:00
LibretroAdmin cc4de1186c RUN_UNTIL: wait nine seconds of the content's frames by default
With no frame count given, RUN_UNTIL waits about nine seconds of the
content's own frames, capped at 600, so its answer arrives inside a
structured interface's ten-second reply deadline at any frame rate:
540 frames at 60fps, 450 at 50fps. A request can still ask for up to
600. The command_frames lane checks the default against its core's
60fps.
2026-10-15 04:13:19 +02:00
LibretroAdmin e1c3cdc9b3 Probe a core path without re-entering the loaded core
libretro_get_system_info() opens the library and calls its
retro_set_environment to learn what it declares. When the path names
the core already loaded, that library is the running one, so the
environment is not called again mid-session: what it declared comes
from the running state and nothing is cached for it. A statically
linked build does the same once its core is initialised.

LOAD_CORE on the running core's own path reaches this, as does the
menu's Load Core. The command_frames harness core counts environment
calls after retro_init, and that lane holds it at zero.
2026-10-15 04:13:16 +02:00
LibretroAdmin a4a72b3c33 RUN_UNTIL: range-check the value and frames before narrowing
The value and frame count are compared as the unsigned long strtoul
returns, so an argument past 32 bits is refused instead of wrapping to
an accepted one, and spaces after the value no longer read as a frame
count of zero. The command_frames lane covers both, along with the <
and > conditions.

The LOAD_CORE comment moves back onto command_load_core, and the docs
say hardcore mode refuses SET_INPUT, not GET_INPUT.

Closes #19833
2026-10-15 03:52:22 +02:00
Rob Loach 297986dd43 GET_INPUT, and core paths checked before anything is unloaded
Three things probing master turned up, and a trim that pays for two of
them.

GET_INPUT [player] answers what a player holds without changing it.
There was no way to ask: SET_INPUT answers in that shape but has to set
something first, and RELEASE_INPUT lets go before it answers, so it
always reads none. A client could not ask what it was holding.

LOAD_CORE answered "The command failed." for a missing path and for a
library that is not a core alike - the generic fallback, because it
returned false without ever saying why. It says which now.

Neither LOAD_CORE nor LOAD_CONTENT unloads the running core for a path
that turns out not to be one. The library is probed first, with
libretro_get_system_info(), which is what core scanning already uses
and which keeps what it finds, so asking twice is free. Checking the
paths stopped a typo costing the game; a stale library or something
renamed still did.

tools/list pays for every tool's description on every session, so the
two new ones are paid for where the list repeated itself:
readOnlyHint is false by default and 37 tools said so anyway, which was
770 bytes, and the memory map named hexadecimal in four columns that
its own description already covers. 48 tools in 22861 bytes, from 46 in
21449.

RUN_UNTIL's conditions move into command.c: a table in the header is a
copy in every translation unit that includes it, and nothing outside
command.c compares them.
2026-10-15 03:52:18 +02:00
Rob Loach 84bc936dc0 RUN_UNTIL: wait on a byte, in one request
A field run cleared one level in over a hundred calls and spent most of
them waiting: run a few frames, read a byte, go round again. Until a
Goomba was twenty pixels away, until Mario landed, until a transition
finished.

RUN_UNTIL <address> <condition> <value> [max frames] runs the content
until the byte at a system address meets the condition and answers with
whether it was met, how many frames ran, and what the byte holds. The
loop runs inside the frontend, where a frame costs nothing, rather than
across a connection per iteration.

It is RUN_FRAMES' job with a predicate instead of a counter: the same
frame_work flag, the same deferred reply, the same one-at-a-time rule,
so nothing new blocks and there is no second state machine. Reading
core memory every frame from the runloop is what rcheevos_test() has
always done, on the same flag.

Two bounds keep it honest. An address nothing maps is refused before a
frame runs, rather than after a wait that could not have ended. The
frames are COMMAND_FRAMES_REPLY_MAX at most, and where they run out
first it answers "waiting" with how far it got, so the next request
carries on instead of the client being told it failed.

The harness core now maps a byte that follows its frame counter, so the
waiting itself is tested rather than only the refusals: already met
answers with no frames run, three frames away answers on the third and
not before, and the cap answers waiting.

RUN_UNTIL costs 1391 bytes of tools/list, which took it past the budget
the end-to-end test holds; the ceiling is raised deliberately, with the
cost recorded beside it.
2026-10-15 03:52:15 +02:00
LibretroAdmin 71518b6ac8 gfx: drop frame-path memsets whose struct is defined by what is written
D3D12's software frame source sets its buffer, which is all its consumer reads without one; the Vulkan views' borrowed texture and the frame-memory staging texture set the image they do not have and the members the copies read. The menu passes' viewports set the full size video_driver_update_viewport takes and fills in the rest, and glcore's whole-frame view writes every member of the API struct.
2026-10-15 03:35:53 +02:00
LibretroAdmin 725efd1dd7 openxr_input: hand joint lane
A runtime stand-in for XR_EXT_hand_tracking and its data source holds
the joint path: a tracker per hand, the data sources asked for, joints
located in the host's space and time in OpenXR's order with each
hand's source flagged, a tracked hand kept without its controller, the
trackers going with the session and a refused one taking its pair, and
the profile's string looked up once per profile.
2026-10-15 03:20:32 +02:00
LibretroAdmin 0501d0a8d2 openxr: locate the hands only for a core that asks
A sample no longer locates the hands. The first
RETRO_ENVIRONMENT_GET_VR_CONTROLLER_POSES locates them at the head's
sample on the drawing thread, and every sample after it does too until
the core's content goes. The interaction profile's string is looked up
when its path changes, the joint order is checked against OpenXR's at
compile time, and an optional instance extension is only enabled while
the list has room, the platform's kept last.
2026-10-15 03:20:32 +02:00
LibretroAdmin 6ca8b435de libretro.h: hand state layout and contract
retro_vr_hand_state puts its pointer ahead of the flags. The docs say
how long interaction_profile lives, that the joint set is fixed, when
the poses were sampled, and how a runtime's bare hand on a controller
profile reads.
2026-10-15 03:20:32 +02:00
LibretroAdmin e4f550ae44 openxr: explicit initialization of the hand tracking structs
The hand tracker, joint, pose and interaction profile structs are
written field by field, as the rest of the OpenXR code is; the input
harness's xrLocateSpace fills a chained XrSpaceVelocity as a runtime
does.
2026-10-15 03:20:32 +02:00
Craig Carnell f8899526b3 openxr: add hand tracking API and implementation
Closes #19831
2026-10-15 03:20:32 +02:00
LibretroAdmin b5063d3208 win32 frontend/companion, smb: explicit initialization of fixed-ABI structs in place of memset
STARTUPINFO, the companion's folder-browser, window-class and DIB structs and the SMB poll descriptor are set member by member; the RtlGetVersion, process, shell-icon and list-item-rect queries read only what their checked calls return. The companion's list-view, header and tab items and its file dialogs keep their memset, their members depending on _WIN32_IE and _WIN32_WINNT.
2026-10-15 03:05:36 +02:00
LibretroAdmin 02c1d0a283 audio: explicit initialization of fixed-ABI structs in place of memset
The WDM-KS property, setup and stream-header structs, the waveOut and IEC 61937 wave formats, the CoreAudio stream descriptions and the PulseAudio sample spec are set member by member; the WDM-KS position, latency and RT-buffer queries, the ASIO channel queries and the Mach timebase read only what their checked calls return. Each packet submitted on the WDM-KS stream writes its header's members rather than clearing it.
2026-10-15 02:56:04 +02:00
libretroadmin b86071c3ae Android/OpenXR Vulkan: free the window on its frame fences
The window's images are only used by the driver's own frames, each
behind a frame fence, so tearing the window down waits on those
fences through vulkan_context_finish_frames(). The XR thread is
still submitting at that point, so a device drain there would need
queue_lock held across it, and would wait on a hardware core's work
as well.

tools/vulkan_idle_wait_check.c now scans android_vk_openxr_ctx.c.
2026-10-15 02:39:56 +02:00
LibretroAdmin 4f80e46ea3 win32: keep a window only for a driver presenting the same way
An OpenGL or Vulkan context tags its window with whether it presents
through DXGI (win32_window_tag_presentation()), so a window left up
across a driver restart is taken only by a driver presenting the same
way; otherwise a new window is made. A core override that turns DXGI
presentation on now applies on every content load, not just the first
one after startup.

The win32_window_proc harness gains a lane keeping a Vulkan window
across a change of presentation.
2026-10-15 02:24:55 +02:00
LibretroAdmin 3d82bae032 Makefile.griffin: link every MSVC release build as a GUI app
Only msvc2003 passed -SUBSYSTEM:WINDOWS, so the msvc6/2005/2010/2012/
2013/2015 release builds linked as console applications and opened a
console window on launch. Debug builds keep the console.
2026-10-15 02:24:06 +02:00
LibretroAdmin 3491a21f47 gdi/win32: explicit initialization of fixed-ABI structs in place of memset
The GDI driver's 32-bit DIB headers, the device notification filter and the pixel-format probe's window class are set member by member; MONITORINFO and the DWM timing query set only their size, their results being checked. DEVMODE and MONITORINFOEX keep their memset, their member names depending on the SDK.
2026-10-15 02:13:32 +02:00
LibretroAdmin e363abc7fa openxr: explicit initialization of fixed-ABI structs in place of memset
The runtime, session, swapchain, frame, tracking and host structs, the graphics bindings and requirements, the swapchain image arrays and the Vulkan staging and submit infos of the cursor upload are set member by member; the event poll writes only the header of its 4000-byte buffer. CODING-GUIDELINES now has a query struct set only its chain when its outputs are read after the call's success is checked, and keeps the memset for structs with bit-fields or reserved members.
2026-10-15 02:05:36 +02:00
LibretroAdmin 3f5382ed43 d3d10/d3d11/video decode: explicit initialization of fixed-ABI structs in place of memset
Texture, buffer and view descriptions, the OPTIONS4 query, the D3D11 video decoder, output view and buffer descriptions, and the Vulkan Video capability chain, format and memory queries, copy regions, readback buffer, barrier and mapped range are set member by member. The blend descriptions, the H.264 standard parameter structs with their bitfield flags, and the DXVA picture parameter blocks keep their memset.
2026-10-15 01:53:04 +02:00
libretroadmin a25f167c53 play_feature_delivery: lock-free download status
The status of a core download - progress, status, active - is one
atomic word with a generation. Java's callbacks update it with a CAS
that only applies while the transaction is active, the core updater
task reads it with one acquire load, and a download claims it through
a CLAIMING bit while it publishes the core and the task to wake. A
late callback can no longer revive a finished transaction, a poll
always sees one whole update, and a callback arriving outside the
frontend's lifetime has no lock to touch, so the init/deinit pair is
gone.

The regression test now also covers status for another core, late
updates after an install, and downloads, Java's thread and a poller
running at once.
2026-10-15 01:40:14 +02:00
libretroadmin 5986b90041 Vulkan/DXGI: drain the presenter on a fence, not vkQueueWaitIdle
ra_vulkan_dxgi_destroy() now uses the same drain as resize: an empty
submit with a fence under queue_lock, and the wait on that fence with
the lock released. A hardware core parked in lock_queue is no longer
held behind a full queue drain, and the destroy no longer depends on
the queue going idle while the core may still have work to submit.

tools/vulkan_idle_wait_check.c now scans win_vulkan_dxgi.c as well,
so an idle wait cannot come back there.
2026-10-15 01:40:11 +02:00
Codex 97ca83eb1c Keep unread WebM laces resident during movie playback
Bound input-page release by the earliest unread packet in both playback and timestamp/audio demuxers. A block walk cursor can otherwise pass laced frames larger than the residency margin.

Exercise Xiph, fixed-size and EBML laces beyond the margin, including a lagging gatherer and rewind.
2026-10-15 01:35:52 +02:00
LibretroAdmin 1412b3d8ac input: explicit initialization of fixed-ABI structs in place of memset
The OpenXR action, space, state, haptics and layer structs, winmm's JOYINFOEX, the raw-input message window class, the udev sensor absinfo fallback, SDL2's left/right haptic effect, the synthesized X button event and the XInput state resets are set member by member. OVERLAPPED, struct input_event and struct sigaction keep their memset, their member names differing between SDKs and C libraries.
2026-10-15 01:32:28 +02:00
Codex 0ab0e85496 Select VP9 GPU frames in WebM thumbnail playback
Publish the D3D11 VP9 backend and expose hardware leases through the WebM animation stream and image_transfer. Resume the same coded component across decoder/export backpressure, pin borrowed packets and pending laces, skip intermediate display exports, and reopen on rewind. Initial unsupported/HDR streams retain software fallback. Primary media_core WebM integration remains separate.

Validate shared-player GPU selection and ownership under Linux sanitizers and native Windows x64/x86. Native RTX 5090 WebM fixtures match 96 frame comparisons through D3D11/D3D12 readers plus eight rewind/lease-after-close checks. D3D11 sample suites, thumbnail/HDR regressions, C89 checks and a complete threadless link pass. Production frames stay GPU-resident; the existing GPU export copy remains.
2026-10-15 01:30:28 +02:00
LibretroAdmin 26ca2824ae CODING-GUIDELINES: initialize fixed-ABI structs by hand rather than with memset 2026-10-15 01:13:07 +02:00
Codex e7fe330150 Decode VP9 profile 0 and profile 2 through D3D11 video
Add explicit fixed-size VP9 sessions using the existing adapter, shared NV12/P010 exports and asynchronous frame fences. Submit bounded DXVA picture parameters and padded coded packets without software reconstruction or CPU pixel transfers. Keep coded dimensions separate from aligned DPB storage and retain per-slot color flags.

Validate packed parameters against the SDK, backend retries/failures and delayed leases with the D3D11 mock, and every sample of four generated 12-frame streams through native D3D11 and D3D12 readers. Show-existing replays require no decoder submission. Linux sanitizer and MinGW/Wine sample suites pass. Automatic playback selection, Vulkan VP9 and HDR remain integration work; output export retains one GPU copy.
2026-10-15 01:09:08 +02:00
LibretroAdmin c9878dd029 vulkan/d3d12: explicit initialization of fixed-layout API structs in place of memset
Create infos, copy and blit regions, clear values, resource and heap descriptions, view descriptions and format queries are set member by member; a view description sets the union member its dimension selects. The pipeline state description and the drivers' own structs keep their memset.
2026-10-15 01:07:44 +02:00
Codex 220e53fee7 Prepare VP9 hardware decode transactions and frame leases
Expose bounded frame-header parsing without software pixel allocation. Preserve sequence color metadata and reset profile-0 depth correctly. Add an owner-thread hardware session with logical reference aliasing, persistent filter/segmentation metadata, hidden/show-existing output, immutable BUSY retries and independent NV12/P010 leases. Display drops bypass surface export. Platform decoder submission and player selection remain separate integration work.

Test C89 mock sessions on Linux with ASan/UBSan and native Windows x64/x86. Compare references and metadata across 96 generated profile-0/profile-2 frames with software decoding, and run existing HDR/SDR fixtures. Wire both VP9 tests and generated-stream checks into CI.
2026-10-15 00:54:50 +02:00
Codex d41299164c Resolve VP9 frame dimensions from current reference slots
Use the slot indices named in the current header when inheriting frame dimensions. Reject absent or invalid-sized selected references before reconstruction. Cover all three selectors across slots 1-7 and explicit sizes in a C89 regression test wired into the format workflow.
2026-10-15 00:35:53 +02:00
Codex 3ae7219107 Test hardware HEVC decode through Vulkan GPU import
Feed real Main/Main10 decoder images into the production importer and compute shader before the oracle waits for readbacks. Verify independent raw-picture hashes, RGBA color equations, shader-input metadata and actual decoder ready/done timelines.

Keep raw-output backpressure tests separate and avoid abandoning frames after successful GPU consumption. Add Linux and MinGW/Wine render-check targets; native RTX 5090 validation covers 120 fixture pictures and 24 long-term-reference pictures.
2026-10-15 00:30:42 +02:00
Codex a9f5d116a3 Validate Vulkan movie GPU presentation through the frontend
Render synthetic NV12/P010 sources through the shipping direct and threaded movie paths. Check stock-chain viewport colors, range/matrix handling, format switches, resize, cached redraws and return to software pixels.

Add a focused Vulkan test target and assert zero CPU frame copies in instrumented GPU phases. Source writes and viewport readbacks remain confined to the test oracle; this does not claim hardware HEVC decoder-to-display coverage.
2026-10-15 00:19:36 +02:00
Codex 0a62d91c52 Hand movie GPU frames across the video-thread ring
Pair movie import descriptors and clears with their refresh in the existing frame ring. The worker reuses renderer imports without transferring video pixels or adding GPU copies, locks or blocking waits.

Preserve staging backpressure, protect movie controls from duplicate replacement, and abandon unconsumed leases on ring drops, busy imports, clear and teardown. Keep unsupported drivers on software and bypass software-buffer cropping for GPU movies.

Exercise 130 leases through the shipping wrapper under ASan/UBSan and TSan, alongside portable Linux/MinGW ownership checks. Move media seek declarations before statements so the full C89 build links.
2026-10-15 00:09:44 +02:00
Codex 05006426cf Present Vulkan movie GPU frames through the filter chain
Connect unthreaded builtin movie playback to the existing NV12/P010 importer. Use its converted texture directly as the whole-frame and view shader source, preserving asynchronous ownership without another upload or GPU copy.

Retain cached redraws and busy retries, retire textures on resize and software transitions, and carry actual source formats into history copies. Cover callback decisions on Linux and MinGW/Wine and verify the GPU filter input descriptor.
2026-10-14 23:54:59 +02:00
Codex e57a4264bf Decode HEVC Main10 and import P010 with Vulkan
Select the session and image formats from the HEVC bit depth and convert packed P010 samples to SDR RGBA on the GPU. Preserve timeline leases, format switching and NV12 support within storage-buffer limits.

Exercise real decoder output against independent Main/Main10 references and run the production importer shader against color, odd-size, format-switch and backpressure oracles. Keep CPU readback confined to tests.
2026-10-14 23:48:43 +02:00
LibretroAdmin 2fcd96a86a Explicit initialization of fixed-layout D3D/Vulkan structs in place of memset
The Vulkan, D3D12 and DXGI descriptors of the DXGI presentation bridge, and the Direct3D 9Ex display mode and present statistics, are set member by member: every input member is written, and the driver-filled query structs set their chain plus each member read back, so a query that leaves them untouched still reads as unsupported. Structs whose layout is not fixed (rpng_hdr_metadata, D3DPRESENT_PARAMETERS across the Xbox SDKs) keep their memset.
2026-10-14 23:38:01 +02:00
Codex eb5bc99006 Decode HEVC Main8 with Vulkan Video
Register HEVC independently of H.264 and reuse existing Vulkan DPB, bitstream rings, timeline submission and leased NV12 outputs. Map short/long-term references to Vulkan slots and share command recording with H.264.

Allow backend parameter queries through the live rh265 source, retaining D3D11 behavior. Preserve VUI field-sequence metadata and gate field output and Main10 until the required presentation formats are supported.

Native RTX 5090 tests match all 60 built-in HEVC frames and 24 long-term conformance frames through public decode/drain and exported GPU images, including held-output backpressure and close with leases outstanding. Six Vulkan H.264 regressions match software. Production HEVC has no CPU readback; an independent output image still requires one GPU crop/copy.
2026-10-14 23:34:38 +02:00
LibretroAdmin 63ead85ba7 task_queue: on Switch, run the worker below the main thread
Horizon never time-slices, and on Switch the task worker shares the
process's default core with the main thread.  Level with it, a handler
that returns without waiting - polling for a callback the main thread
runs - holds the core, the callback never runs, and the frontend
freezes with the transfer at 0%.  The worker now lowers itself one step
below its creator there, as it does on 3DS, so tasks run whenever the
main thread waits.

samples/queues/task_queue_fifo_prio_test runs the queue's Switch code
on a host under SCHED_FIFO pinned to one CPU, the priority calls stood
in.  A handler polling for a main-thread callback finishes in two
frames, and frames keep coming at 5 ms while a task works; with the
worker level with the main thread the first lane livelocks and the
second sees a 519 ms gap.  The lanes need CAP_SYS_NICE and skip
without it.

Fixes #19591 together with the handlers parking on their sub-tasks.
2026-10-14 23:34:01 +02:00
LibretroAdmin f16ea3a02e samples: keep the 3DS task queue sample out of the mingw-w64 lane
The task_queue samples under mingw-w64 + Wine job builds every
queues/task_queue_* directory for Windows.  The 3DS homebrew is built
with devkitARM only, so it goes by ctr_task_queue_prio_test, and the
libretro-common samples job skips it under that name.
2026-10-14 23:34:00 +02:00
libretroadmin 9cf3eb18d6 Cut down on unnecesary memsets for struct initialization 2026-10-14 23:31:19 +02:00
libretroadmin c05f8f7e11 Cleanups 2026-10-14 23:08:02 +02:00
libretroadmin ba0834b1be d3d9: present timing for the threaded presenter under Direct3D 9Ex Presentation
With Direct3D 9Ex Presentation on, the D3D9 drivers report how long each
frame's present waited for the display (get_last_present_wait): with
V-Sync on, the time in Present and the WaitForVBlank beside it, the
frame counted as out on return only where it waited for the vblank
after Present. The threaded presenter then leaves the wait out of the
render time it holds the core back by, instead of starting the core a
wait earlier each frame. The vblank a present went out on comes from
the swap chain's present statistics while they describe that present,
as D3D11 reads DXGI's, and from the compositor otherwise.

Off, or without Ex, the drivers report no wait and the compositor's
vblank, as before. samples/gfx/d3d9_ex checks the timing in each way a
frame waits, reads the statistics through the swap chain's Ex table,
and checks the hand-laid Ex tables and statistics against the SDK.
2026-10-14 22:59:07 +02:00
Codex 15a5029ca9 Build and validate Vulkan HEVC session parameters
Retain bounded single-layer VPS metadata directly from escaped input and expose it without pixel access. Map actual VPS/SPS/PPS data into stable caller-owned StdVideo storage, including diagonal scaling lists and checked explicit reference sets.

Add portable mapping and malformed-VPS regressions plus a separate loader/device session test. Native RTX 5090 checks accept Main/Main10 sessions, memory binding and parameter objects for ten fixtures. Decode submission and renderer integration remain separate work.
2026-10-14 22:43:42 +02:00
LibretroAdmin 142ab23661 CI: skip the 3DS task queue sample in the libretro-common samples job
queues/task_queue_ctr_prio_test is a 3DS homebrew built with devkitARM,
which the Ubuntu runner does not carry; it runs on the console or an
emulator.
2026-10-14 22:42:47 +02:00
LibretroAdmin d5c92b5314 task_queue: on 3DS, run the worker below the main thread
The 3DS kernel never time-slices: of two ready threads on a core, the
higher priority one keeps it until it waits.  rthreads starts a worker
one step above its creator, and on Old 3DS the task worker shares a
core with the main thread, so a handler that keeps working held the
core for as long as its task ran.  A millisecond's sleep after every
pass handed the core back, a frame at a time at best: with passes of
25 ms the main thread drew 22 frames a second.

The worker now lowers itself one step below the thread that started
the queue.  Tasks run whenever the main thread waits - for the vblank,
for audio, in task_queue_wait - and the sleep goes.  The Emscripten
half of it stays.

samples/queues/task_queue_ctr_prio_test is a 3DS homebrew that has
the main thread draw frames while a task works, and wait on one.  On
Old 3DS, before: 78 ms between frames, 22 of them over the task's
second; without the sleep and with the old priority, one frame in
1019 ms.  Now 17 ms, 62 frames, and the task takes as long as before.
New 3DS, where the worker has a core of its own, passes either way.
2026-10-14 22:29:26 +02:00
Codex b11baeb886 Expose HEVC sequence metadata for hardware parameters
Preserve SPS profile/tier/level, temporal-layer DPB limits and resolved reference sets for Vulkan parameter creation. Keep these queries separate from the large existing parameter snapshot and avoid pixel access or allocation.

Validate layer and DPB bounds while preserving the full unsigned latency field. Cover inferred limits, sublayer PTL alignment, predicted reference sets and invalid queries.
2026-10-14 22:28:53 +02:00
Codex 2e5d6248e0 Negotiate Vulkan video codecs per decode queue family
Separate common video extensions from H.264/HEVC extensions and retain the selected family codec mask through both device creation routes and caching. Prefer shared coverage while retaining existing H.264 behavior on split families, and guard H.264 backend registration.

Add portable negotiation regressions and a read-only native profile probe. RTX 5090 Main8/Main10 capability queries succeed; actual Vulkan HEVC session creation and decode submission remain the next stage.
2026-10-14 22:19:03 +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
libretroadmin e348adfb8e d3d9: Direct3D9Ex devices, and frames the GPU reads where they lie
Both D3D9 drivers make a Direct3D9Ex device where d3d9.dll has one,
resolved at run time and declared locally, so an SDK from before Vista
still builds them; a system without it, a device it will not make, and
RETROARCH_D3D9EX=0 keep the plain device and everything as before. An
Ex device has no managed pool, so every texture the CPU writes goes
through d3d9_tex.h: managed on a plain device, and on Ex a default-pool
texture with a system-memory twin hung off it, locks taking the twin
and the unlock of a write sending the region written up with
UpdateTexture. An overlay's quad buffer is dynamic there.

On Ex the drivers give the threaded wrapper frame memory: its slots
live in blocks the drivers own, and a frame inside one goes up with
UpdateSurface from a system-memory texture made over it, so the GPU
reads it where the core or the wrapper wrote it and the CPU copies
nothing. An event query, polled and never waited on, says when a block
is the core's again.

samples/gfx/d3d9_ex reads back what reached the GPU on both kinds of
device; Windows-wine-menu runs it, and runs the menus and a core
writing into lent and wrapper memory - threaded too - on d3d9_hlsl with
and without Ex, as Windows-wine-views does the views.
2026-10-14 22:18:43 +02:00
LibretroAdmin 4791072084 task_queue: on GCD, run a due task with dispatch_async; time only a wait
The GCD runner hands a task that is due now to the global queue with
dispatch_async(), and gives it a timer source only once it parks or is
scheduled for later; a wake turns a wait on the timer into a
dispatch_async().  A per-task record, under running_lock, keeps one run
pending or in progress, and the timer's handlers hold the record rather
than the task, so a fire after the task stopped finds nothing to run.
A pass that makes progress costs what dispatch_async() costs.

A finishing task moves from the running queue to the finished one under
both locks, so a wait on the queue never finds it in neither.

task_queue_park_test: deinit with a task parked, which the next init
runs; a wait lane that widens the gap between the runner's two locks;
the child-callback lane sets its handler before the push.
2026-10-14 22:08:01 +02:00
LibretroAdmin e674363719 menu_wine: a screen holds still only once something is drawn
The first launch in a fresh Wine prefix can draw its first frame after
the settle time, and two blank reads agree, so a blank screen was taken
as the steady one and made the reference. run.py and swfb.py now keep
reading until a drawn screen holds still; one that never draws still
reaches the nothing-drawn check at the deadline.
2026-10-14 22:07:44 +02:00
Codex 9a10ea5098 Fix HEVC preview header rejection and decode long-term references
Accept matching in-band SPS/PPS repeats without replacing active metadata or copying payloads. Repeated headers in a 4K Main8 file prevented hardware preview submission; generated Main/Main10 repeat fixtures and the native D3D11/D3D12 readers now cover that path.

Add software long-term DPB retention, current lists and reference-class-aware TMVP/AMVP, preserving class snapshots in queued slices. Keep class bits in motion-field padding. All 500 Main8 conformance pictures match FFmpeg across software scheduling modes; focused tests cover retention, POC resolution and predictor classes.
2026-10-14 22:07:04 +02:00
Codex 1ffc4c1fdf Validate native HEVC long-term decoding against conformance output
Add an opt-in external MP4/NV12 oracle and a local fixture preparation script with reproducible hashes. Require active long-term references and compare count, geometry, display order and every output sample through D3D11 and D3D12 readers. The first 24 LTRPSPS_A_Qualcomm_1 pictures pass on the native device; no external media is bundled.
2026-10-14 21:53:20 +02:00
libretroadmin 516889eb76 d3d9: lock only the frame's rectangle of its texture
A frame went into its texture under a lock of the whole texture, which
the core's largest geometry sizes as a square - 2560x2560 for a core
that reaches 2560x1600 - so the runtime uploaded the whole managed
texture every frame, 26 MB for a 16 MB frame, and the video thread's
frame took milliseconds longer than it had to, and spiked.

Both drivers upload through d3d9_frame_upload now, the views' direct
upload included, which locks the frame's rectangle, and the whole
texture only when the size changed and what is past the frame is
cleared. The software framebuffer loan locks the size asked for, and
the whole texture only for a window, another pitch or a new size.
samples/gfx/d3d9_sw_fb checks which rectangle each locks.
2026-10-14 21:42:39 +02:00
LibretroAdmin e85b1405f3 task_bluetooth: wait out the end of the scan window parked
The queue may start a timed task up to half a millisecond early; the
scan's handler parks for whatever of the window is left, and a cancel
still wakes it and ends the window.  bluetooth_scan_test counts the
scan's passes.
2026-10-14 21:39:52 +02:00
LibretroAdmin bde84fc13a task_translation: the auto-translate follow-up waits on the main thread
The task that waits for the speech or the narration to end, then sets
the next request off, runs on the main thread, where the frontend state
it reads and writes lives, and parks for 50 ms between looks.  It is not
pushed when its mode cannot be allocated.  translation_reply_test runs
it in speech mode with the speech playing and then over.
2026-10-14 21:39:52 +02:00
LibretroAdmin d79f6a8194 task_steam: a cancelled DLC install reports nothing
A cancel ends the install task without the 'core installed' message the
end of a successful download posts.  steam_dlc_park_test cancels an
install in progress.
2026-10-14 21:39:52 +02:00
LibretroAdmin 8553536bec task_steam: ask for the DLC download's progress at a progress bar's rate
Steam gives the install task nothing to wake it with, so between asks
for the download's progress it parks for 250 ms.  steam_dlc_park_test
builds the task against a stand-in mist header, plays a download and
counts the asks.
2026-10-14 21:39:52 +02:00
LibretroAdmin 774e0b5b86 play_feature_delivery: wake the install task on each status update
play_feature_delivery_download() takes the ident of the task watching
the download, and the JNI status callbacks wake it once the status lock
is released.  The install task parks between updates.  The JNI harness
checks a status and a progress update wake the watcher; the switch-cores
harness holds an install and counts the status polls while it waits.
2026-10-14 21:39:52 +02:00
LibretroAdmin 2211bb3fd4 task_http_emscripten: park while the fetch is in flight
The handler parks until the fetch settles; the settle publishes the
response through an atomic and wakes the task by ident.
http_emscripten_park_test builds the unit natively against stand-in
Emscripten headers, plays the browser, and checks the task waits parked
and retires at the settle.
2026-10-14 21:39:52 +02:00
LibretroAdmin b4c26c9716 google_drive: the OAuth poll task parks on its poll, woken by an outcome
While a token poll is in flight the polling task parks; the poll's
callback publishes the outcome through atomics, clearing poll_pending
last, and wakes the task by ident when the outcome is final.  A pending
reply leaves the next poll to its interval.  A cancel ends the task once
no poll is in flight, since the callback writes into the context the
task's cleanup frees.  gdrive_backup_test drives the handler and the
callback through a poll, a cancel and a final reply.
2026-10-14 21:39:52 +02:00
LibretroAdmin a7dac33f35 netplay NAT traversal: park while a transfer runs or no reply has come
While the device has a transfer in flight the task parks, and the
transfer's callback wakes it; busy is an atomic, cleared by the callback
on the main thread and read by the task on the worker.  Before a UPnP
discovery reply arrives the task looks again every 10 ms.
nat_task_serialisation_test has a threaded lane with the fake device made
asynchronous, as natt.c is.
2026-10-14 21:39:51 +02:00
LibretroAdmin 05c6b3a1e1 task_core_updater: park the waits on sub-tasks, woken by their callbacks
Every wait on a sub-task parks: for 100 ms where it copies the sub-task's
progress into its own, for a one-second watchdog where only the
completion ends it.  The completion record carries the waiting task's
ident and signal() wakes it; the list and download handles carry
theirs for the HTTP and decompress callbacks.

update_installed_scan_test checks that the wait on the core list ends at
its wake; the two lanes that hold a task while the queue frees its
sub-task keep that sub-task's wake back until the free.
2026-10-14 21:39:51 +02:00
LibretroAdmin bb2418ae04 task_pl_thumbnail_download: park while a transfer runs
Both thumbnail tasks park while their transfer runs, and the transfer's
callback wakes them; it publishes completion through an atomic of its
own.  The callback writes into the task's handle, so a cancelled task
waits for it before finishing.  pl_thumbnail_wait_test runs the
per-entry handler against the real queue: its passes over three
transfers, and a cancel with one in flight.
2026-10-14 21:39:51 +02:00
LibretroAdmin 077d55a51c task_file_transfer: a WebM sound goes to the mixer handler
The image task and the mixer both load WebM.  A WebM handle the image
task marked goes to the image handler, any other to the mixer's, which
builds the sound's data from it.  task_file_transfer_test loads a WebM
sound and a WebM video and checks which handler each one reaches.
2026-10-14 21:39:51 +02:00
LibretroAdmin 20002ca4c7 task_audio_mixer: the windowed feeder parks between looks
After a feed the decoder has a full lookahead ahead of it, so the feeder
parks for 100 ms - long enough to stop it running back to back for as
long as the music plays, short enough to cover the lookahead at 20 MB/s.
A stopped stream parks too; a lost window or a released sound is dealt
with at once.  mixer_wfeed_test drives the handler's steady state.
2026-10-14 21:39:51 +02:00
LibretroAdmin 4134eace32 task_movie: a recording waits for a state load from the main thread
The record task only waits for an in-flight state load to retire, which
happens on the main thread, so it runs there and looks once per check.
bsv_replay_init_test starts a recording on the threaded queue with a
load in flight.
2026-10-14 21:39:51 +02:00
LibretroAdmin ad2d961e10 task_save: a state load waiting for the core's second frame parks
Until the frame loop has run the core twice the load handler parks for
about a frame and looks again, instead of running back to back on the
worker.  save_state_io_test's threaded lane pushes a load before the
second frame and counts its parks.
2026-10-14 21:39:51 +02:00
LibretroAdmin 8739b75d00 task_overlay: a failed load finishes
The error state sets FINISHED along with CANCELLED, so a load that fails
retires and its callback runs, with no overlay.  The overlay harness has
a lane that loads a pack with a malformed descriptor.
2026-10-14 21:39:51 +02:00
LibretroAdmin f2349bd3d5 task_queue: let a waiting handler park until it is woken
A handler that returns unfinished because it waits on something now
calls task_queue_park() with a watchdog horizon, and whatever ends the
wait calls task_queue_wake() with the task's ident.  Both meet under the
running lock, so a wake that lands during the pass cancels the park it
asks for.  A cancel or a reset wakes a parked task too, and
task_queue_wait() counts a parked task as running.  The unthreaded
queue runs a handler once per check and ignores parks.

On GCD each task runs from one timer source, which a park, a wake or a
cancel retargets; a start time is passed to it in nanoseconds.

task_queue_park_test covers a wake from another thread, from a child's
callback and from inside the pass, cancel, reset, the wait, the horizon
and the unthreaded queue; GCD=1 builds it against the GCD runner.
2026-10-14 21:39:51 +02:00
Codex 04c5f3b887 Retain HEVC references selected by every queued slice
Pin the union of active slice reference lists until queued decoding releases the context. Later slices can select additional pictures through list modification; inactive entries must not acquire reader holds. Cover reader lifetime, deduplication and release alongside the existing slice snapshot regression.
2026-10-14 21:36:51 +02:00
LibretroAdmin a002e202b3 XVideo: draw without waiting on the server
The XVideo driver draws each frame into one of two images, put on a
connection of its own whose completion events say when an image may be
drawn into again; while the adaptor still reads both, the frame is not
shown rather than waited for. The window's size comes from its events,
so no frame makes a round trip. Images are made at the first frame's
size, attach failures are caught and the driver stops trying, and a
fresh image is black rather than grey.

The menu is blended over the game's frame, or over black without
content, by its alpha: RGUI gives the driver RGBA4444 with transparency,
as it does the other drivers. The statistics are drawn, messages go in
each eye's area where the UI is per eye, and the mouse pointer can be
shown and hidden. xv_standin queues the completion event a put asks
for, as the server sends it.
2026-10-14 21:02:39 +02:00
Codex ad53c224eb Preserve HEVC reference metadata in queued slice jobs
Snapshot and restore reference POCs, active-list counts and motion-prediction settings with each slice so later parsing cannot replace the values used by earlier queued slices. Cover distinct B/P snapshots and temporal scaling, and verify two-slice Main/Main10 streams against FFmpeg across software scheduling modes.
2026-10-14 21:01:46 +02:00
libretroadmin 111cddf99b D3D12: drain the queue before the swap chain is resized
A frame waits only for its predecessor's frameSignal, which is queued
behind the frame's command list but ahead of its Present. When the
swap chain was resized - as it is on a software core's first frame -
ResizeBuffers released back buffers the last Present still held, and
the device faulted (the debug layer reports it inside ResizeBuffers).
The resize now drains the queue first, a lifecycle drain like the
others on the allowlist.

tools/d3d12_idle_wait_check.c now also fails on any ResizeBuffers in
the driver with no d3d12_queue_drain before it in its function, so
this drain cannot be taken out as an idle wait again.
2026-10-14 20:58:43 +02:00
Codex 8091c510d8 Validate HEVC reference arithmetic and combined DPB capacity
Check reference-set indices and POC calculations before narrowing, and initialize unused predicted-set entries for deterministic snapshots. Count all signaled short- and long-term references against DPB capacity, even when unused pictures are absent.
2026-10-14 20:52:21 +02:00
libretroadmin 7cec269588 d3d9: lend the core the next frame's texture
GET_CURRENT_SOFTWARE_FRAMEBUFFER on both D3D9 drivers, without the
threaded wrapper (which lends its own buffers): the next frame's
texture is lent locked, the core writes its frame where the upload
would have copied it to, and the frame unlocks it and draws without a
copy. A window pushed from inside the loan is moved to the corner,
past it cleared only when the size changed or the core wrote there; a
frame from anywhere else ends the loan and is copied as before. The
cached frame, which names the loan, is replayed out of the texture
holding it, and that texture is parked rather than released inside a
frame, let go once the cached frame is retired. Views decline it.

samples/gfx/d3d9_sw_fb checks the loan against a stand-in texture, and
Windows-wine-menu runs the threaded video harness core in it on
d3d9_hlsl - still, cropped, and through the driver growing its
textures - against D3D11's copy.
2026-10-14 20:42:17 +02:00
libretroadmin caaab477f3 d3d9: write menu quads and glyphs straight into the vertex ring
A quad drawn on its own, a line of glyphs and d3d9_cg's overlay quad
were built in a stack or scratch array and copied again by
DrawPrimitiveUP into the runtime's own buffer, the glyphs after a
memset of the scratch. They are written where they are drawn from
now: room in the menu's dynamic ring, locked by d3d9_menu_vbuf_map
with DISCARD when it starts over and NOOVERWRITE otherwise, and drawn
with DrawPrimitive. The strips take the same helper, so every lock of
the ring goes through one place; a line longer than the ring keeps the
scratch array.
2026-10-14 20:42:14 +02:00
libretroadmin 8d05f9d76d d3d9_cg: start the menu's vertex ring over with DISCARD
The menu's vertex ring went back to its start with a NOOVERWRITE lock,
on a buffer that was not dynamic, so once a frame drew more than its
1024 vertices - with a notification up - the vertices of draws still
pending were rewritten and the GPU drew whole-screen triangles from
them. Both D3D9 drivers now make the ring in d3d9_menu_vbuf_create, a
dynamic buffer of 8192 vertices, and d3d9_cg starts it over with
DISCARD as d3d9_hlsl does.

samples/gfx/d3d9_menu_ring runs each driver's display draw against a
stand-in device that remembers which vertices pending draws read, and
fails a lock that rewrites one, a NOOVERWRITE lock of a static buffer,
or a driver making a ring of its own; Linux-samples-gfx runs it.
2026-10-14 20:42:07 +02:00
Codex 854abf07cd Parse and retain HEVC long-term references for hardware adapters
Expose SPS-indexed and slice-signaled long-term metadata, including cumulative MSB cycles. Resolve and retain matching DPB slots transactionally and carry their reference class into DXVA picture and slice sets.

Cover parser metadata, wrap, ambiguous and missing references, SPS cycle boundaries, later-slice consistency and serialized long-term flags. Software reconstruction still refuses actual long-term sets; real long-term GPU stream validation remains pending.

Linux sanitizer, MinGW/Wine, native Windows DXVA mapping and the existing short-term device oracle pass. The software oracle variants, compile matrix, stack checks and full threadless build pass. No new production pixel copies, readbacks or waits are added.
2026-10-14 20:39:34 +02:00
Roman VasilyevandLibretroAdmin 74d02ceaaa menu: preserve and select existing text when editing (#19809)
* menu: add an option to preserve text when editing

Prefill string settings and playlist names before opening built-in or native keyboards. Expose the behavior in Menu Controls, enabled by default, with a persisted menu_input_preserve_text setting.

* menu: distinguish new text input from editing

Use an explicit dialog type to preserve existing values when editing. New-entry, search and authentication dialogs start empty. Remove the menu option and configuration setting.

* input: select existing text when opening an editor

Typing replaces the initial selection, while arrow keys collapse it for editing. Draw the selection in each menu driver and select the initial text in native Android and iOS fields.

* text entry: no stored passwords on screen, and native buffers stay put

A password setting opens its editor empty rather than with the stored
secret selected in plain text.

The Android and iOS keyboards put a fixed 512-byte buffer under the
line. Opening it now sets the line's capacity to that buffer and marks
it fixed, so an append from a hardware or on-screen key stops at its
end instead of writing past it or moving it from under the keyboard.

frontend_overhead's text selection lane opens a string setting and a
password setting, types over a selection, and appends to a fixed line.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-14 20:41:07 +02:00
Codex 17b1b184e7 Preserve HEVC VUI color metadata and MP4 full range
Expose SPS VUI color metadata and inherit missing or unspecified MP4 matrix/transfer fields while preserving explicit container values and range. Recognize PQ/HLG before hardware opt-in and recheck eligibility when reopening the decoder.

Honor full range in SDR scalar/SSE2/NEON conversion and translate MP4 boolean range for the shared 10-bit/HDR blitters. Cover software colors, NV12/P010 metadata, HDR fallback, container overrides and parser queries. Execute the existing HEVC fixture hash oracle in make check.

Linux sanitizer, MinGW/Wine and native Windows regressions pass, including native HEVC device comparisons. The compile matrix, stack budget, full threadless build and HEVC software oracles pass. NEON cross-compiles; no ARM device execution is claimed. Production copies and readbacks are unchanged.
2026-10-14 20:11:42 +02:00
Acts1631andLibretroAdmin 7e446630cc Input: validate keyboard remap key IDs (#19808)
* Input: validate keyboard remap key IDs

Remap files can provide arbitrary integers for keyboard mappings.
These values index the mapper key bitset and key_button array during
input polling, allowing out-of-bounds reads and writes.

Clamp invalid values while loading remaps and reject invalid stored
values before the polling path accesses either array.

* input: check keyboard remaps where they are loaded, not every poll

The remap file is the only place a keyboard mapping comes from that is
not one of the menu's own keys, and it now stores anything out of range
as RETROK_UNKNOWN, so the poll goes back to one compare per mapping.

samples/config/remap_keyboard_ids loads a remap file through the real
input_remapping_load_file() and checks keys kept and out-of-range ones
cleared, on the button and stick paths.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-14 19:57:57 +02:00
Codex a1d8d3b365 Accept empty HEVC long-term sets and validate GPU reference streams
Parse an enabled but empty long-term set instead of refusing the slice solely on its SPS flag. Permit the corresponding empty SPS case in the hardware owner state; actual long-term references remain unsupported.

Use realistic-size Main/Main10 device fixtures and exercise I/P, B-pyramid, WPP and cropping through the registered decoder and both D3D readers. Check software output against independent FFmpeg frame hashes and compare every hardware sample and display order. Tiny parser/mock fixtures remain separate because real drivers may expose no configuration at 64x64.

Native Windows D3D11/D3D12 device comparisons, Linux sanitizer and MinGW/Wine regressions, all three HEVC software oracle variants, compile matrix, stack checks and the full threadless build pass. No new production pixel copies or readbacks are introduced.
2026-10-14 19:49:33 +02:00
Acts1631andLibretroAdmin eeeaa77a37 Cheats: validate ranges before applying memory writes (#19807)
* Cheats: validate ranges before applying memory writes

Cheat files can specify addresses outside the core memory map or place a
multi-byte value across the end of a memory region. The apply path used
the resulting offset for unchecked native memory reads and writes.

Resolve the complete access range against one core memory buffer before
reading it and before every repeated write. Skip entries and stop repeat
sequences when their requested range is invalid.

* cheats: one region lookup for every memory access

Cheats and cheat searches reach core memory through cheat_region_seek,
which finds the region holding a whole item and keeps it, so a walk
that stays inside a region does not search the region list again. The
three search paths read through it too: an item crossing a region's end
is left out of the matches when a search starts, and browsing one
leaves the values as they were. A condition whose address cannot be
read does not hold, so the cheat it guards does not run.

samples/cheats/cheat_regions maps three regions of awkward sizes, each
allocated exactly, and runs cheats and searches across their edges
under the sanitizers. The .gitignore rule for the cheats directory is
anchored to the root so the sample can be tracked.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-14 19:51:27 +02:00
libretroadmin 15059f1db8 CI: compare the menus across drivers under Wine
Each menu driver is drawn by D3D8, D3D9, D3D10, D3D11, D3D12, GL and
GLCore in a mingw-w64 build under Wine, and each screen is compared
with D3D11's by the mean colour of a grid of blocks. A solid quad
sampling the wrong texture, as d3d9_hlsl's batched quads did, or a
gradient split along the other diagonal, as both D3D9 drivers' single
quads did, fails it; text and edge filtering stay well inside it.
2026-10-14 19:36:02 +02:00
libretroadmin 31981e2bcb d3d9: split single quads along the batch's diagonal
A quad drawn on its own went out as top left, top right, bottom left,
bottom right, so a four-colour gradient was split along the other
diagonal from the batched strips and every other driver, and XMB's
background came out darker through the middle on both D3D9 drivers.
Write its vertices in the strip order the batch draws in: bottom left,
bottom right, top left, top right.
2026-10-14 19:35:58 +02:00
libretroadmin 62abfc7ce5 d3d9_hlsl: bind the white texture for untextured menu strips
Batched solid quads reach the display driver as strips with no
texture, and the strip path only bound one when a texture was set,
so backgrounds, sidebars and separators sampled the font atlas left
bound by the last text draw. Bind on every strip draw, falling back
to the white texture as the quad path and d3d9_cg already do.
2026-10-14 19:35:54 +02:00
Codex 1f0d2209f5 Rename the builtin player core and helpers to media
Move libretro-webm/webm_core.c and its private GPU header to libretro-media/media_core.c and media_hw.h. Rename core helper functions, state, constants and GPU sink APIs to media_ and libretro_media_ prefixes; update internal routing, normal and Griffin builds, and test/CI references.

Use media as the reported library name and log label. Keep public libretro entry points, serialized data format and the webmplayer build option compatible.

Linux sanitizer core/GPU handoff tests, MinGW/Wine host tests, threadless/no-MP4 tests, tree checks and CI selftest pass. The full threadless RetroArch build links.
2026-10-14 19:30:22 +02:00
Codex e3878d2a9b Try remaining compatible D3D11 decoder configurations
Continue enumerating compatible configurations when CreateVideoDecoder fails, and retain the minimum target count from the configuration that succeeds. Cover H.264 and HEVC fallback and exhausted-configuration cleanup with the COM mock.

Match the native HEVC oracle target count to playback (SPS DPB limit plus one). Native Windows and Wine mock checks pass; native H.264 device comparisons pass through D3D11 and D3D12. HEVC device setup still skips on this host.
2026-10-14 19:30:22 +02:00
Codex 29ec2c2406 Connect MP4 GPU movies to unthreaded D3D presentation
Reuse NV12/P010 imports and feed their converted textures through the existing D3D11/D3D12 hardware frame and shader paths. Compatible presets read directly; history-dependent presets retain their GPU copy.

Defer decoder startup until video readiness, close sessions before driver teardown and restore playback against the new backend. Mark cached GPU frames for screenshots/recordings and preserve software fallback for unsupported consumers, filters and unresolved CPU capture.

Test production handoff decisions, reference cleanup, cache replay, software format restoration and D3D12 output states. Threaded/Vulkan movie hooks, full movie UI/shader validation and real HEVC reference execution remain separate acceptance gates.
2026-10-14 19:16:27 +02:00
LibretroAdmin 5211a38a98 rvorbis: bound codebook size as libvorbis does
A codebook is accepted only when ilog(dimensions) + ilog(entries) is at
most 24, the limit libvorbis applies, so every stream it decodes still
opens and every table sized from a codebook stays under 2^24 entries.
That keeps the lookup and multiplicand tables bounded in int, and setup
allocations small, without separate overflow checks at each site.

rvorbis_codebook_test pins the bound for every dimension count and
decodes a real stream in full.
2026-10-14 19:16:14 +02:00
LibretroAdmin efd24d8d96 XVideo: views
The XVideo driver draws a core's views, laid out on the window: each
view is sampled straight from the frame into the adaptor's image in its
YUV format (YUY2, UYVY or I420), at twice the scale that draws no view
larger than it is, and the adaptor scales the image to the window.
Interlaced's views go into their eye's rows; anaglyph's eyes are blended
a row at a time into the image. With the menu open the views are
converted at the window's size and the menu blended over them, per eye
where the UI is. The driver takes the core's pixel format from its init
(XRGB8888 frames were drawn as RGB565), opens fullscreen at 0x0 at the
desktop's size, and read_viewport gives back what the adaptor shows.
video_views_cpu_draw_placements() and video_views_cpu_cut() share the
composer's placement drawing and cutting.

samples/gfx/xv_standin is an XVideo adaptor for an X server with none,
preloaded in front of libXv; the views end-to-end cases run the driver
against it on Xvfb, in CI for each of its formats.
2026-10-14 19:14:36 +02:00
LibretroAdmin 1e6b12d003 scaler: copy only each row's own pixels at one size
A same-format conversion at one size copies each row's width of pixels,
by the format's size, through conv_copy_16, conv_copy_24 and
conv_copy_32. The output can be a rectangle inside a larger image: the
rest of its rows stays as it was, and the last row stops at the
rectangle's edge instead of a stride past it, outside the buffer when
the rectangle reaches its end.

pixconv_test copies 5x3 into a 12x4 image at each pixel size and checks
every byte outside the rectangle.
2026-10-14 19:14:35 +02:00
Acts1631 774ceaac73 Audio: validate Vorbis codebook table sizes (#19816)
Vorbis setup headers control codebook entry and dimension counts. Their
product was narrowed to int by the setup allocator before the expansion
loop wrote the full table, allowing malformed headers to overflow heap.

Compute lookup and expansion counts in 64 bits and reject values that
cannot be represented by existing allocation and loop types. Avoid
out-of-range float-to-int conversions while calculating lookup1 sizes.
2026-10-14 19:10:46 +02:00
Rob LoachandLibretroAdmin c2e5bce004 MCP: structured results, and LOAD_CONTENT checks before it unloads (#19815)
* LOAD_CONTENT: check paths before unloading

* mcp: results carry structuredContent

* LIST_CORES filters by content path

* Structured rows for core options and memory map

* MCP: do not advertise a cacheable tool list

* SET_INPUT holds several controls at once

* A row opening with MORE is a row, not a cursor

* MCP: fewer tools, and rows a client can rely on

structuredContent and outputSchema never reached a client over stdin
and stdout. The gate read the MCP-Protocol-Version header, or the
version the stateless revision sends with every request; stdio has
neither, so the revision agreed at initialize was parsed, used for the
handshake reply and then dropped. It now holds for the session, which
over stdio is the connection.

A command answers with structuredContent exactly when it declared an
outputSchema, so a client reads the schema it was handed rather than
probing each reply. SET_INPUT, RELEASE_INPUT and GET_LOG declare one
now.

The "MORE [next]" line was read by a literal in the server. The shape
of a reply names its own trailers instead - the lines that carry a
number about the reply rather than a row of it - so GET_LOG's NEXT and
SKIPPED are the same mechanism rather than two more special cases, and
a reply says nothing about being cut short where it cannot be. A reply
whose lines are prose declares no columns: GET_LOG's page runs to 60k,
and a row apiece would answer with it twice.

Thirty-four commands are no longer offered as tools. A hotkey that
steps a selection - the next state slot, the previous shader, the next
cheat - is a hotkey because a person presses it while watching the
screen. Where a command takes the value outright that one is the tool,
and stepping towards it is only a way to get the value wrong; where
nothing reports the selection either, there is nothing worth offering
yet. READ_CORE_RAM and WRITE_CORE_RAM go too: an achievement address
looks like READ_CORE_MEMORY's system address and is not one. The disc
and the menu are kept, because stepping them blind is all there is and
a disc swap is part of playing a game. All of them are still commands
on every line interface.

The tool list is 45 tools and 20k, from 79 and 23k, and the end-to-end
test holds it to a budget: every description is context a client pays
for before it does anything.

* PRESS: hold, run and let go in one request

A tap was four requests: SET_INPUT, RUN_FRAMES, SET_INPUT to release,
RUN_FRAMES again. Each is its own HTTP connection against four slots,
so a field test spent about 25 of them crossing a menu and about 10 on
one jump.

PRESS <player> <control>[+<control>...] <frames> holds the buttons,
runs that many frames and lets them go, answered once the last frame
has run. It is RUN_FRAMES' job with buttons held through it: the same
deferred reply, the same one-at-a-time rule, the same pause the runloop
applies to frame advance, so nothing new blocks and no second state
machine exists. Whatever ends the frames lets the buttons go, including
the error that ends them early.

Axes stay SET_INPUT's. They are held at a value rather than pressed,
and SET_INPUT with RUN_FRAMES is still the way to hold a control across
several steps.

SET_INPUT and PRESS read the same request - a player, controls joined
by "+", and a number - so that parse is one routine now, and both
resolve every control before either acts on any.

* CHANGES: the MCP and command work in this series

* GET_STATUS: say what it actually answers with

The description promised the content's CRC32 and the reply has never
carried one: it is the system and the content's file name, separated by
a comma.

HELP prints that text on the line interfaces and tools/list hands it to
every MCP client, where it is the prose a client reads to decide how to
take the reply apart. One told to expect three comma-separated fields
gets two.

Adding the CRC instead would be worse. Working it out means hashing the
content, which for a core using need_fullpath reads a whole disc image
from disk - task_movie.c already carries that note, having hit it - and
GET_STATUS is the command a client polls. It would also change the
reply for every client that reads the line today.

* command: descriptions that match the replies

tools/list hands a command's description to every MCP client, and HELP
prints it on the line interfaces: it is the prose a client reads to
decide how to take a reply apart. Five of them described a reply that
was not the one sent.

SET_INPUT and RELEASE_INPUT answer with three fields - the player, the
buttons held and the axes held - and the description named two. A
client splitting on what it was told got the player where it expected
the buttons. RELEASE_INPUT did not say it answered at all, and with no
player it lets go for everyone while answering for the first of them,
which is worth saying rather than leaving to be discovered.

GET_LOG can open with SKIPPED and a count where the lines asked for had
already been overwritten, which the description did not mention: a
client reads it as a log line.

READ_CORE_MEMORY and WRITE_CORE_MEMORY both answer - with the bytes,
and with how many were written - and neither said so.

* READ_CORE_MEMORY: format bytes without snprintf

Both memory reads wrote their reply with an snprintf per byte, to turn
one byte into two hexadecimal digits. At COMMAND_READ_NBYTES_MAX that
is 16384 calls, which costs 1.5ms on an aarch64 Pi - a tenth of a frame
spent formatting, on the main thread, in the command an assistant reads
memory with most. A nibble table is 0.02ms for the same bytes, 68 times
faster, and the output is identical for all 256 values of a byte in
both of the formats the two sites used.

The harness core now maps its memory, because nothing in the tree did:
READ_CORE_MEMORY, WRITE_CORE_MEMORY and GET_MEMORY_MAP had no test that
reached past "no memory map defined", so the formatting this changes
was uncovered. The test asserts the replies byte for byte, including
every digit of a byte, a read clamped at the end of its region, and one
outside every region. Breaking the table deliberately fails three of
them.

* PRESS: leave SET_INPUT's holds in place

PRESS lets go only of the buttons it pressed itself, so a button a
client already holds with SET_INPUT is still held once the press ends.

* command, mcp: trim comments and order new struct members

Drop the superseded LIST_CORES and reply-splitting comments, shorten
the new comments to what stays true, and order the new struct members
by alignment.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-14 18:59:27 +02:00
LibretroAdmin 809ddc1152 CONTRIBUTING: add policy on core authors and upstream developers
Backport of libretro-super commit df51c9cc53386e615cc9f65513cb8606fab86d0b.
Libretro does not make statements about, endorse, or vet the personal
views or conduct of core authors or upstream developers. Documentation
describes the software only, and decisions about including or removing
a core are based on licensing, technical, and maintenance criteria.
2026-10-14 18:32:06 +02:00
Rob Loach 44b042e86b Stop tracking generated Wayland protocol sources (#19827)
generate_wayland_protos.sh generates 24 protocols and configure runs
it; 20 of them are in .gitignore and untracked, and these four were
committed instead. The copies in the tree say wayland-scanner 1.22.0,
so anyone whose scanner differs gets eight modified files as soon as
they configure, and a stray "git add -A" commits them.
2026-10-14 18:32:39 +02:00
Codex cc730318a8 glcore: resize recording PBOs before readback
Reinitialize the recording ring before issuing a readback at a changed viewport extent. Retire pending fences and reset the slot index before choosing the destination. Cover growth and shrinkage with scripted allocation-capacity checks at glReadPixels.
2026-10-14 18:16:50 +02:00
Codex 0dc3dc4981 Add the movie core side of a nonblocking GPU handoff
Offer an opt-in private sink for MP4 hardware frames. Retain busy leases, abandon them on reset/seek/unload, and follow software fallback when reopening the decoder.

Distinguish input starvation from decoder backpressure so a resident movie does not mistake GPU busy for EOF. Validate the real core lifecycle and save/restore with portable HEVC backend and sink mocks.

No frontend sink is installed yet; ordinary movie playback remains software. Primary-frame renderer/shader integration is the next step.
2026-10-14 18:12:49 +02:00
Codex 53280a4f5a camera: honor FFmpeg frame color range and matrix
Configure the cached scaler from each decoded frame's color metadata, with decoder metadata fallback and format defaults for unlabelled RGB, gray and YUVJ. Keep direct producer-buffer conversion. Cover full-to-limited range and BT.709-to-BT.601 transitions, plus unlabelled grayscale, checking every scaled output pixel.
2026-10-14 18:06:48 +02:00
LibretroAdmin 783d9eb39f ozone: refresh the header icon when its surface retires a texture
The header icon is a handle borrowed from a surface, and a surface
loaded again retires its old texture once the new one lands.
gfx_surface_retired() counts the textures surfaces let go; the header
keeps the count it was set at, and ozone_render takes the handle again
when the count moves, before a frame can draw a retired texture. The
frame's fallback to the logo stays local to the frame, so the video
thread writes nothing into the menu's state.

menu_icon_ownership draws through a counting display driver whose
handles are never reused and fails on any draw of a texture that is
not live; gfx_surface_still checks that the count moves on a
replacement and a free, and not on an update in place.
2026-10-14 17:57:30 +02:00
Codex 162e6b2409 camera: pace FFmpeg transient read retries by frontend demand
Park the capture worker on EAGAIN or EINTR until another frontend poll requests a frame, using an eventcount and poll epoch to avoid lost wakeups. Stop wakes parked capture before joining. End non-transient read failures after one diagnostic. Cover idle and poll-driven retries, quiet transient errors, terminal errors, parked shutdown and recovery.
2026-10-14 17:56:48 +02:00
LibretroAdmin f4b524783d samples/gfx: build gl2_pbo_readback for Windows too
The GL 1.1 entry points the GL drivers link directly move into
gl2_pbo_readback/gl11_fakes.h, shared by gl2_pbo_readback and
gl_last_pass_fbo: on Windows they are supplied as opengl32 import
pointers, so both harnesses build with mingw-w64 and run under Wine.
gl2_pbo_readback now counts glFinish calls and fails on any rather
than exiting at the first.
2026-10-14 17:46:51 +02:00
Codex 5f04c748fc glcore: fence asynchronous recording readbacks
Poll each recording slot's completion without waiting before mapping or reusing its PBO. Retain pending slots, fence the slot actually written, and retire recording fences on replacement and teardown. Disable the ring if fencing fails. Cover pending and completed reads, failed queries, replacement, resize and fence cleanup with scripted GL.
2026-10-14 17:45:45 +02:00
Codex a129c08b94 Validate P010 imports on a native D3D12 queue
Run the production import functions with a same-adapter D3D11 producer. Check color conversion, shared-handle caching, unsignalled producer fences, ordered consumer release and refusal without submission under pressure.

Extend the HEVC device oracle to read Main10 planes through D3D12. Readback and blocking waits remain test-only. The queue fixture preallocates output; real HEVC decoding and movie-core presentation remain separate acceptance gates.
2026-10-14 17:44:37 +02:00
Codex 242ef2f2d4 glcore: reject failed recording buffer maps
Return failed readback when PBO mapping fails, restoring the pack binding and context without unmapping an unmapped buffer. Reject corrupted data reported by a failed unmap too. Cover destination preservation, slot retirement and shared-context ownership in the scripted GL fixture.
2026-10-14 17:35:13 +02:00
LibretroAdmin 2e4b775169 video: drop the cached frame when the core's pixel format changes
A frame cached in the old format is the old format's size, so a reinit
or menu redraw replaying it in the new one read past its rows.

The threaded_video harness's pixel format lane switches its core to
0RGB1555 and XRGB2101010 and checks the driver gets every pixel of the
first row converted, at the converted pitch, without the wrapper and
on the video thread with it. The pacing-rate lane now changes the
timing its fake driver reads only with the video thread idle.
2026-10-14 17:09:19 +02:00
LibretroAdmin b41779ad5f scaler: a converter made before its size is known comes up again
An unscaled context runs straight between the caller's buffers, so
only a scaled one allocates the intermediate frames. A converter
generated at 0 x 0 and given each frame's size later - the frontend's
0RGB1555 to RGB565 one - is unscaled and comes up with nothing
allocated; video_driver_pixel_converter_init() got NULL from it, and
0RGB1555 cores reached the driver unconverted.

The pixconv sample makes such a converter, checks it owns no frames,
and converts through it.
2026-10-14 17:09:15 +02:00
Codex 992d6c6c7f Import P010 video frames on D3D11 and D3D12
Convert shared P010 planes on the GPU with correct limited/full-range normalization. Freeze renderer import capabilities before offering automatic HEVC Main10; NV12-only consumers retain software fallback.

Validate production shared-handle imports, cache guards, constant uploads and SDR shader colors on Windows, plus automatic Main10 lease ownership in decoder mocks. Normal presentation has no CPU pixel readback; the existing decoder crop/copy remains.
2026-10-14 17:03:50 +02:00
Codex e2b6d7e554 Preserve preview source format across D3D texture fallback
Describe incoming pixels by the same format used at initial upload, rather than the texture storage format selected by the device. This prevents red/blue swaps in D3D10 previews when BGRA textures fall back to RGBA and fixes the equivalent D3D11/D3D12 update paths. Exercise production D3D10 load/update and DXGI conversion with forced storage formats and nonblocking update failures.
2026-10-14 16:43:07 +02:00
Codex 3a320cc204 Validate Main10 export and GPU frame format transport
Exercise P010 profile, texture, submission and lease lifetime with the D3D11 mock. Extend the capable-device oracle to compare ten-bit P010 samples with software, and check surface-player format forwarding and refusal.
2026-10-14 16:43:07 +02:00
Codex 2f77d757c6 Export explicit HEVC Main10 P010 hardware frames
Select the Main10 decoder profile and P010 textures for explicit HEVC sessions. Carry image format through GPU frame transport and label NV12 producers explicitly. Keep automatic player selection limited to Main8 until P010 renderer imports are available; reject unknown frame formats.
2026-10-14 16:43:07 +02:00
LibretroAdmin ce52a30774 samples/gfx: run gl2's recording reader through the PBO harness
gl2_pbo_readback now calls gl2's poke record_read, as the threaded
wrapper does: the first call starts the readbacks, then a full ring is
read back as B,G,R,X rows as the PBO holds them and as converted BGR24,
bottom row first either way.
2026-10-14 16:30:10 +02:00
LibretroAdmin bbb40cc9a8 video: read GPU recordings back as BGRX through the threaded wrapper
The recording reader moves onto the poke interface as record_read and
takes the format to read: BGR24 bottom up, or 32-bit B,G,R,X in the
driver's own row order. The threaded wrapper reads back in the format
the recording was opened for, so a recording started under Threaded
Video on Vulkan or GL is opened for 32-bit frames and the encoder gets
the wrapper's buffer as read, with no conversion on the main thread.
A recording switched from unthreaded BGRX to the wrapper keeps its
32-bit rows the same way.

The threaded_video harness's record reader lane installs a driver with
a scripted record_read and checks both formats arrive pixel-exact in
the driver's row order, straight from the wrapper's buffer.
2026-10-14 16:17:08 +02:00
Codex 50356acf26 Route MP4 HEVC hardware frames through stream ownership
Keep pending samples across hardware backpressure, advance the display clock for exported pictures, and retire untaken leases on rewind or close. Exercise decoder retries, DPB pressure, drain, software fallback and untouched CPU output with portable mocks, plus the registered D3D11 path.
2026-10-14 16:09:49 +02:00
Codex d2f2090095 Connect rh265 decoding to registered hardware sessions
Open hardware before the first picture and retain borrowed packet metadata across decode and export backpressure. Transfer displayed leases once, bump prior output without consuming a pending packet, and retain software decoding when device setup refuses the stream. Register the D3D11 Main8 offer and size sessions from the SPS DPB requirement.
2026-10-14 16:09:49 +02:00
LibretroAdmin 187b5b4a4b d3d9: draw a viewport-sized last pass straight to the backbuffer
The HLSL and Cg drivers append a stock pass after a preset's last pass
only when the image would differ drawn straight to the backbuffer: a
scale other than the viewport at 1.0 (GFX_FBO_SCALE_IS_VIEWPORT()), or
a floating-point target.

samples/gfx/d3d9_last_pass runs both drivers' preset setup on scripted
presets under mingw-w64/Wine; Direct3D 9 being Windows-only, there is
no Linux build of it.
2026-10-14 15:41:35 +02:00
Codex 79361d14de Exercise HEVC D3D11 submissions and shared-frame lifecycle
Cover profile selection, optional matrices, busy retries, driver failures and unread-frame retirement with the D3D11 mock. Add a capable-device oracle comparing an imported Main picture with software through shared handles and fences.
2026-10-14 15:39:37 +02:00
Codex 6646ad748c Add D3D11 HEVC Main callback backend
Reuse the NV12 output pool and fence retirement for HEVC Main. Pack borrowed slices directly into driver buffers and select raw-bitstream configuration 1; busy submission leaves logical ownership unchanged. Keep Main10 and player registration pending until P010 transport and device validation are available.
2026-10-14 15:39:37 +02:00
LibretroAdmin d05dda0fdd samples/gfx: run glcore's GLSL/Cg chain through the last-pass lane
samples/gfx/gl_last_pass_fbo replaces gl2_last_pass_fbo: the scripted
GL, shader backend and cases move to gl_last_pass_fakes.h, and a second
test builds gl3.c's chain through gl3_create_fbo_targets() against the
same cases, so gl2 and glcore are held to one decision for the last
pass. Both run under ASan+UBSan and under mingw-w64/Wine.
2026-10-14 15:28:40 +02:00
LibretroAdmin dc06b1faf9 gl2, glcore: draw a viewport-sized last pass straight to the backbuffer
The GLSL/Cg render chains of gl2 and glcore give the last pass an FBO
of its own, drawn to the backbuffer by the stock pass after it, only
when the image would differ drawn straight there: a scale other than
the viewport at 1.0, an FBO format, or the preset's feedback pass
being the last one.  GFX_FBO_SCALE_IS_VIEWPORT() in video_shader_parse.h
is that scale test, shared with the slang chains.

samples/gfx/gl2_last_pass_fbo builds gl2's chain against a scripted GL
and shader backend for each case, under ASan+UBSan and under
mingw-w64/Wine.
2026-10-14 15:24:13 +02:00
Codex b0d09d8383 Exercise Main and Main10 submissions through HEVC hardware callbacks
Run borrowed fixture access units through busy decode/output callbacks, direct DXVA packing, owner commit and caller frame abandonment. Keep the software reconstruction comparison to prove payload preservation; no GPU backend is registered by these tests.
2026-10-14 15:20:53 +02:00
Codex d590f12572 Dispatch HEVC hardware callbacks with borrowed packet retries
Freeze packet and metadata identity while decode is busy, commit accepted pictures once, and retain output slots until export succeeds. Transfer frame leases to the caller, support cancelling unaccepted work, and stop failed sessions without waiting or copying pixels. Check busy decode/output, pressure, stale input, cancellation and lease cleanup.
2026-10-14 15:20:53 +02:00
Codex 410b0a4188 Connect HEVC owner plans to DXVA picture buffers and reference oracles
Map transactional POC/reference plans into validated DXVA submissions without changing ownership. Extend Main/Main10 pipeline checks with busy retry and one-time commit, and sequence real B-frame oracle streams through the owner DPB/output model.
2026-10-14 14:59:16 +02:00
Codex bc51a2634a Prepare and commit HEVC hardware picture ownership transactionally
Derive full POC and retained short-term reference slots without advancing state during busy retries. Commit accepted submissions once, retain display-pending pictures, and order output across POC resets. Keep GPU fence and exported-frame lease handling with the backend. Check wraparound, pressure, reference pruning, RASL skips and stale commits.
2026-10-14 14:59:15 +02:00
LibretroAdmin d81a9adbf5 slang: render a viewport-sized last pass straight to the backbuffer
The Vulkan and glcore chains give a preset's last pass its own render
target, copied to the backbuffer by a hidden pass, only when
glslang_last_pass_needs_target() says the last pass needs one: a scale
other than viewport at 1.0, a render target format asked for by the
preset or by the shader, or a pass reading the last pass's feedback.
A last pass declaring scale_type = viewport at 1.0 renders direct, as
one without a scale_type does.

samples/gfx/slang_last_pass_target holds the decision for each case,
under ASan+UBSan and under mingw-w64/Wine.

Closes #19825
2026-10-14 14:51:23 +02:00
Codex d7306e8242 Verify Main and Main10 borrowed access-unit submission pipelines
Exercise parser snapshots, picture parameters and direct Annex-B packing on small generated HEVC fixtures. Compare software reconstruction from original length-prefixed input and packed backend buffers, with no runtime media-tool dependency. Add the pipeline test to Linux and Windows sample checks.
2026-10-14 14:35:08 +02:00
Codex 5a584e1727 Pack borrowed HEVC slices directly into backend input buffers
Add Annex-B start codes, DXVA short descriptors and final 128-byte padding while preserving escaped payload bytes. Preflight every span and capacity so failure leaves buffers unchanged. Avoid a second host compressed staging buffer and validate byte layout against the SDK.
2026-10-14 14:35:08 +02:00
Codex f0ce938d8f Describe borrowed HEVC access units without advancing picture ownership
Split explicit Annex-B or length-prefixed input into borrowed NAL spans, then validate one picture's slice order and shared metadata. Preserve immutable packet storage across busy retries, reject in-band parameter changes, and expose mixed-slice intra classification without payload copies or allocation. Check real oracle access units before software decoding.
2026-10-14 14:35:08 +02:00
LibretroAdmin 532831cacb CI: views-sdl3 installs ImageMagick
The menu and pointer cases read the screen under Xvfb with ImageMagick's
import, which the SDL3 job did not install, so they reported "no
screenshot". run.py now refuses up front under Xvfb when import is
missing, naming it.
2026-10-14 14:33:57 +02:00
Codex f5b004590a Map HEVC short-term RPS into caller-owned DXVA reference slots
Resolve used POC deltas against live GPU slots without taking frame leases or changing the DPB. Validate missing, ambiguous and overflowing references, enforce DXVA set bounds and keep mixed-slice intra flags consistent. Failed conversion preserves the submission descriptor.
2026-10-14 14:15:48 +02:00
Codex 053f4ac31e Parse borrowed HEVC slice headers for hardware submission
Read escaped NAL headers without allocating or copying the compressed payload. Export resolved short-term RPS deltas, predictor counts and syntax bit lengths without advancing picture ownership. Bound header parsing and reject malformed escaping or alignment. Exercise explicit, SPS and predicted RPS and parse real oracle slices before software decoding.
2026-10-14 14:15:47 +02:00
Codex 8cab584349 camera: clean up partial FFmpeg startup safely
Avoid flushing a missing decoder when startup fails after opening the input. Free the temporary options dictionary on every failure path. Cover stream discovery failure, invalid decoder calls during cleanup, and decoded pixels after a successful restart.
2026-10-14 14:14:56 +02:00
LibretroAdmin 381a221444 win32: an in-place exclusive fullscreen toggle at 0x0 takes the monitor's size
A fullscreen size left at its default of 0x0 stands for the monitor's own size. The in-place exclusive toggle now resolves it that way before changing the display mode and placing the window, as a driver starting up does, so the window stays on screen at the monitor's size instead of becoming a 0x0 window.

The win32_window_proc Wine lane toggles exclusive fullscreen in place at 0x0 and checks the window is kept, visible, monitor-sized and comes back windowed.
2026-10-14 14:13:21 +02:00
Codex 678bb1200d camera: refresh FFmpeg scaler for decoded frame changes
Use each decoded frame's dimensions and pixel format to refresh the cached scaler. Keep the requested output size and convert directly into the producer buffer. Cover a real H.264 stream changing from 32x16 YUV420P to 64x32 YUV444P, checking uniform pixels from both segments.
2026-10-14 14:11:00 +02:00
Codex fd67da1ab0 camera: stop FFmpeg capture after device removal
Exit the capture worker on ENODEV instead of repeatedly reading and logging a removed device. Preserve existing handling of other read errors and allow explicit stop/start recovery. Cover one failed read and successful restart with the live-driver fixture.
2026-10-14 14:03:29 +02:00
Codex 8356a1f5a6 Map parsed HEVC parameters into validated DXVA submissions
Convert codec-neutral parameter snapshots into DXVA coding fields and scan-order scaling buffers. Preserve picture and reference ownership and leave descriptors unchanged on unsupported parameters. Test SDK bytes, picture state retention and failure paths.
2026-10-14 13:59:53 +02:00
Codex aff79cb36f Expose HEVC parameter snapshots for hardware adapters
Export parsed PPS/SPS metadata and effective raster scaling lists without allocating or accessing pixels. Convert conformance offsets to luma samples and reject invalid crops or missing sets. Exercise Main/Main10, PPS overrides, independent snapshot ownership and failure clearing.
2026-10-14 13:59:53 +02:00
Codex 9ecdcecf6f camera: interrupt FFmpeg input reads when stopping capture
Install the shutdown callback before each input-open attempt and clear stale shutdown state before reopening. Treat a requested read interruption as normal worker completion.

Cover a controlled blocking read, failed-open fallback and restart, verifying cancellation occurs without the fixture timeout.
2026-10-14 13:47:39 +02:00
Codex a6b48a5ee6 Convert raster HEVC scaling lists to DXVA scan order
Write effective coefficients directly to the submission buffer, preserving separate DC values and rejecting invalid matrices. Check all size classes against independent scan tables without changing borrowed inputs.
2026-10-14 13:44:39 +02:00
Codex ccd4de3256 Check HEVC submission buffers against native DXVA SDK layouts
Cover 8/10-bit picture fields, signed values, full DPB capacity, scaling matrices and malformed slice metadata. Integrate the portable and Windows ABI tests into the existing D3D11 sample checks.
2026-10-14 13:38:13 +02:00
Codex 4f8da12179 Prepare validated HEVC DXVA picture, scaling and slice buffers
Marshal Main/Main10 4:2:0 submission data without SDK headers or pixel reads. Validate geometry, reference-set indices and supported syntax; borrow Annex-B payloads and account for backend padding.
2026-10-14 13:38:13 +02:00
Codex e1795847f5 camera: drain FFmpeg decoder output and retain blocked packets
Receive all available frames after packet submission, retry retained input after decoder output makes progress, and flush delayed frames at input EOF. Keep direct conversion into producer-owned slots and stop checks between frames.

Cover buffered multi-frame output and a real H.264 B-frame source whose final frames require decoder drain. Treat already-drained EOF as normal during teardown.
2026-10-14 13:38:00 +02:00
Codex 3ee5aab8dd Compare restricted H262 reconstruction against generated reference pictures 2026-10-14 03:29:07 +02:00
Codex e4168fa815 Exercise H262 intra fragments and unsupported-mode gates 2026-10-14 03:29:07 +02:00
Codex e0d2c23a62 Reconstruct restricted progressive H262 intra pictures
Parse MPEG-2 extensions atomically, gate unsupported picture modes, and apply 12-bit escape levels and parity mismatch control. Keep sequence headers intact across fragmented input.
2026-10-14 03:29:07 +02:00
Codex ccebba2f61 camera: route only selected video packets to FFmpeg decoder
Retain the selected stream index and release other packets without decoding or converting them. Cover an audio-first, video-second lavfi source, verifying video pixels and packet routing without additional copies.
2026-10-14 03:23:55 +02:00
Codex cab849528e Exercise DVD LPCM headers, partial mixer reads and every-frame seeks 2026-10-14 03:19:20 +02:00
Codex c85ef3aaab Preserve DVD LPCM packing across short mixer reads and exact seeks
Decode whole blocks directly into output where possible. Retain one bounded block for partial reads, and defer intra-block seek skipping until the next read.
2026-10-14 03:19:20 +02:00
Codex 3747e19dd2 Read the complete DVD LPCM private-stream header 2026-10-14 03:19:20 +02:00
Codex 0f9945b53c camera: stop FFmpeg capture worker at end of input
Treat EOF as completion rather than retrying the finished source and logging indefinitely. Keep the last published frame available, and assert one EOF read in the finite-source fixture.
2026-10-14 03:12:30 +02:00
Codex 021d090c4f Compare worker-backed DVD LPCM decoding against generated PCM references
Exercise 16/24-bit VOB audio through VFS reads and borrowed block framing. Compare all decoded samples and EOF with FFmpeg fixtures, without adding runtime dependencies.
2026-10-14 03:09:09 +02:00
Codex 6c79cccaeb Borrow DVD LPCM blocks and assemble only packet-boundary fragments
Route one selected substream through bounded block views for rlpcm decoding. Test independent wire vectors, every split across all channel counts and depths, format changes and output bounds.
2026-10-14 03:09:09 +02:00
Codex a38a5e59c7 Correct DVD LPCM high-depth sample packing
Decode and encode DVD 20/24-bit groups separately from raw PCM. Expose complete channel-aligned block geometry and round DVD requests to whole blocks.
2026-10-14 03:09:09 +02:00
LibretroAdmin 7957ff6a57 video_views e2e: fail when the driver asked for is not built
RetroArch goes on with another video driver when the one asked for is
not in the build, and the cases then pass on that driver's results:
an SDL 1.2 run against a build with only SDL2 passed every case on
Vulkan. A case now fails with the driver named.
2026-10-14 03:08:07 +02:00
LibretroAdmin 54dd0d3c02 Framebuffer and KMS drivers: views
drm, dispmanx, sunxi, omap, exynos and oga draw a core's views, each
laid out on the screen, which every one of them now reports as its
output size, and the viewport is the whole screen while they draw.

The drivers whose display hardware scales a surface to the screen -
drm's plane, dispmanx's element, sunxi's layer and omap's overlay -
compose the views on the CPU at the scale that draws no view larger
than it is, and let that hardware do the enlarging. The composer they
share with xshm is video_views_cpu_compose() in video_views_cpu.h,
which xshm now uses too: each placement cut to its own eye's area,
anaglyph and interlaced through a canvas of both eyes that is then
blended. sunxi and omap at 32 bits compose straight into the page that
is scanned out; omap at 16 bits converts as it goes, and lowers the
scale as far as its overlay's memory needs. drm and dispmanx compose
into a surface of their own, set up beside the frame's and given back
to it when the views end.

The blitters scale each placement straight from the frame: exynos with
the G2D from the GEM buffer the frame is already copied to, oga with
the RGA into an upright surface the size of the screen that one more
blit turns onto the panel, so a turned panel needs no arithmetic of
its own. video_views_cut_source() gives both the part of a view a cut
placement shows. Neither blends eyes: anaglyph, interlaced and, on
oga, a turned core draw the frame whole.

omap's menu buffer now grows with the mode it is scaled to; it stayed
320x240 while a core's larger frame was scaled into it.

The compile matrix builds drm, dispmanx, exynos and oga, against stubs
of the VideoCore userland, libdrm's exynos module and librga where
none of those are installed; libdrm-dev is added to its packages. The
unit test covers the composer and the cut, and the drm harness the
views surface's lifetime and its pixels.
2026-10-14 03:08:07 +02:00
Codex 44e9e63e20 camera: reset FFmpeg worker shutdown state on restart
Clear the atomic shutdown flag before launching the new capture worker. Require actual converted pixels after two starts, rather than accepting the initialized black frame as restart progress.
2026-10-14 03:04:56 +02:00
Codex ab412b2ce7 camera: keep FFmpeg device options out of decoder setup
Probe streams and open the decoder using codec parameters rather than capture-device options, so requested video_size cannot replace actual input dimensions. Advance leftover-option iteration and test a 34x18 source converted to 17x9.
2026-10-14 03:04:52 +02:00
libretroadmin ad3567e6d8 Add this to .gitlab-ci.yml 2026-10-14 02:58:33 +02:00
Codex e0df32f050 Decode generated VOB AC-3 through the worker-backed VFS stream
Validate CRCs and PCM output using the existing rac3 decoder. Track borrowed versus assembled frames and reject a trailing incomplete frame in the fixture harness.
2026-10-14 02:46:31 +02:00
Codex 36b95a0901 Exercise DVD AC-3 fragmentation and borrowed frame ownership 2026-10-14 02:46:31 +02:00
Codex 3c3674b9fa Frame DVD AC-3 audio with borrowed whole frames
Reuse rac3 header parsing and caller-owned storage for spanning frames. Acquire the selected substream at its first access unit and require reset after discontinuities.
2026-10-14 02:46:31 +02:00
Codex c7b36e8b04 camera: convert FFmpeg frames directly into producer slots
Point swscale output at the producer-owned triple-buffer slot, eliminating the staging image and full-frame copy. Align and pad output allocations for direct conversion, and retain slot ownership through callback completion.

Guard against staging copies and check slow-callback stability and odd-size converted pixels in the synthetic camera fixture.
2026-10-14 02:46:04 +02:00
LibretroAdmin 27ddadb899 Wii and GameCube: views
Both GX drivers, os/gekko's and libogc's, draw a core's views: each
placement a quad of its part of the frame's texture, the GPU sampling
the tiles the frame is uploaded to anyway. The frontend lays the views
out in square pixels, the EFB's lines as wide as the TV's aspect makes
them, which the drivers now report as their output size; the quads are
stretched back to the EFB's width. Side by side and top-bottom draw
RGUI and messages in each eye's area; anaglyph and interlaced, which
need a canvas, draw the frame whole. The viewport is the whole EFB
while views draw.
2026-10-14 02:45:21 +02:00
Codex c0a5c6620d Exercise worker-backed VOB demux across files and generated DVD fixtures 2026-10-14 02:06:46 +02:00
Codex 8521204179 Pump worker-backed DVD reads through a borrowed program-stream parser 2026-10-14 02:06:46 +02:00
Codex fca382c31e Borrow DVD program-stream input and expose private packet views 2026-10-14 02:06:45 +02:00
Codex 86fe410309 tests: recycle PipeWire capture storage on buffer return
Overwrite returned capture storage in the padded native-frame case, verifying the callback consumes all pixels before the producer can reuse the buffer. This guards borrowed-frame lifetime without changing production behavior.
2026-10-14 01:59:30 +02:00
Codex 8ed4d763a3 Cover DVD pool scheduling, contention and queued cancellation 2026-10-14 01:54:24 +02:00
Codex 33ac765670 Schedule DVD VFS reads with bounded thread-pool posting 2026-10-14 01:54:24 +02:00
Codex c306c53e75 Offer bounded thread-pool posting without overflow locks 2026-10-14 01:54:24 +02:00
Codex 5250fb770b camera: borrow native PipeWire frames during raw callbacks
Deliver aligned native ARGB frames directly from capture storage, preserving chunk offset and pitch. Hold the capture buffer until the callback returns, then queue it once. Retain scaler conversion for other formats.

Verify pointer identity, padded-row pixels, untouched conversion output and callback-before-return ownership in the scripted driver fixture.
2026-10-14 01:54:04 +02:00
Codex 88a3bd986c camera: match PipeWire formats to native pixel byte order
SPA format names describe byte order, while scaler ARGB/ABGR formats describe native 32-bit words. Advertise BGRA/RGBA on little-endian hosts and retain ARGB/ABGR on big-endian hosts. Update negotiation and pixel regressions to use the corresponding native layouts.
2026-10-14 01:44:27 +02:00
Codex 87c7802a45 camera: clear PipeWire conversion state on format removal
Release output and scaler resources when PipeWire clears the format, and suppress conversion until a new output buffer is configured. Cover stale-frame suppression, repeated clear, unrelated parameters and renegotiation.
2026-10-14 01:41:12 +02:00
Codex 8eed0bab03 Open and retire owned DVD VFS sources with real-file coverage 2026-10-14 01:40:58 +02:00
Codex 0e2e9d2b6e Service DVD reads through VFS on an external I/O worker 2026-10-14 01:40:57 +02:00
Codex ccd9e75193 Bind DVD read requests to borrowed VFS handles 2026-10-14 01:40:57 +02:00
Codex db99971432 camera: release PipeWire output buffers on format replacement
Free the previous output allocation before replacing it during format negotiation. Exercise repeated format and size changes with leak checking enabled.
2026-10-14 01:38:11 +02:00
Codex fcd66f7851 scaler: support direct ABGR to ARGB conversion
Reuse the symmetric red/blue swap converter for the reverse direction, allowing advertised PipeWire ABGR camera frames to convert.

Cover every alpha value with padded input/output rows in SIMD and scalar builds, and exercise ABGR format negotiation and pixels in the camera fixture.
2026-10-14 01:35:27 +02:00
Codex a8307627d4 camera: match PipeWire strides to negotiated pixel formats
Use the negotiated pixel size for both buffer requests and scaler input strides. Cover ARGB, BGR and YUY2 negotiation, with pixel-exact ARGB and BGR conversion using the packed-stride fallback.
2026-10-14 01:32:03 +02:00
Codex f58941b823 Poll DVD backend completions and retain cancellation ownership 2026-10-14 01:30:12 +02:00
Codex 4b7d49612a Dispatch DVD segment reads into reserved consumer storage 2026-10-14 01:30:12 +02:00
Codex 5be5ce438e Bind DVD source geometry to nonblocking backend handles 2026-10-14 01:30:12 +02:00
LibretroAdmin daa49cea17 XShm: a full driver
The XShm driver ("x11") draws the menu, messages and statistics, and
keeps up with its window. The menu's texture is copied, scaled into the
game's viewport and blended by its alpha, into each eye while views
draw side by side or top-bottom; messages and statistics go through
the font driver. The window is an ordinary X11 one: titled, closable,
fullscreen through the window manager, its images made again when it
is resized. Frames are drawn into two images in turn on a connection of
their own, an image drawn into only once its completion event says the
server is done reading it, and a frame shown only when one is: never
written under the server, never waited for. A server that cannot map
the shared memory, a remote one, gets XPutImage. The driver frees what
it made, X resources included.

The views e2e cases run on XShm in full, the menu and pointer cases
with them.
2026-10-14 01:28:17 +02:00
Codex 65ad54d06f camera: deactivate a streaming PipeWire camera on stop
Request deactivation while streaming; a paused stream is already stopped. Cover streaming stop, paused stop, restart, and already-started behavior in the scripted driver fixture.
2026-10-14 01:27:32 +02:00
Codex 3ba28b13c2 Initialize DVD read requests from checked title targets 2026-10-14 01:24:17 +02:00
Codex 1066ca33f8 Track DVD read completions and drain cancelled requests 2026-10-14 01:24:17 +02:00
Codex b4b168831b Prepare nonblocking DVD reads into consumer-owned buffers 2026-10-14 01:24:17 +02:00
Codex 92723d9260 camera: honor PipeWire frame offsets and row strides
Use the SPA chunk offset and stride when converting camera frames, validate the contiguous image extent, and restore the negotiated stride after conversion. Return every dequeued buffer once.

Add pixel-exact coverage for offset padded rows and a subsequent packed frame with implicit stride.
2026-10-14 01:24:13 +02:00
Codex 3e7ef00b4d Resolve checked DVD title targets into VOB read spans 2026-10-14 01:13:02 +02:00
Codex 3e135689c0 Restrict DVD VOBU target lookup to cell sector bounds 2026-10-14 01:13:02 +02:00
Codex bfba7efc08 Resolve unique DVD cell address identities 2026-10-14 01:13:02 +02:00
Codex 9d3e36ba3e tests: declare the thumbnail fixture process ID helper
Include unistd.h for getpid on POSIX and process.h with _getpid on Windows. Clang rejects the undeclared call and stops the animated-thumbnail sanitizer fixture before it can run.
2026-10-14 01:12:51 +02:00
Codex 4fd34bd2c2 Read borrowed DVD VOBU maps and find sector starts 2026-10-14 01:03:41 +02:00
Codex 46ea655227 Read borrowed DVD cell address records 2026-10-14 01:03:41 +02:00
Codex e84f32d7e1 Expose DVD menu and title navigation address table spans 2026-10-14 01:03:41 +02:00
Codex 7d05867728 gfx: unload retired textures after cancelled replacements
A replacement upload can park the old texture while its memory is lent to a producer. If the surface is freed in flight, unload that retired handle along with the replacement when completion destroys the surface.

Exercise cancellation with lent memory and both image-decode completion orders.
2026-10-14 01:03:06 +02:00
Codex 6fec3d2e81 Bound DVD title cell byte ranges against VOB geometry 2026-10-14 01:00:01 +02:00
Codex 4adb60c95e Map DVD title byte requests into bounded VOB spans 2026-10-14 01:00:01 +02:00
Codex 85bcc6918d Validate borrowed DVD title VOB segment geometry 2026-10-14 01:00:01 +02:00
Codex f8c865b616 gx2: snapshot the DRC preference for each frame
Capture the Wii U DRC preference on the main thread when building video_frame_info_t. Use that snapshot for GX2 view selection instead of reading live settings on the video thread.

Keep the new snapshot field and assignment specific to Wii U.
2026-10-14 00:56:16 +02:00
LibretroAdmin 8bdcd609a9 XShm: views
The XShm driver draws a core's views into its image: each view scaled
straight out of the frame into its placement, anaglyph and interlaced
through a canvas whose halves the CPU blends into the image. A frame
drawn whole is now scaled into the viewport as well, where it was
copied in unscaled, past the image's rows when wider than the window,
and as 32-bit whatever its format. Fullscreen takes the desktop's size,
where the 0x0 it is asked for aborted on the empty shared segment. GPU
screenshots read the image, cut to the output size the driver reports.

The scaler cache and the cut placements the SDL 1.2 driver kept are
gfx/video_views_cpu.h now, for both. The views e2e cases run on XShm
in the SDL 1.2 job, but for the menu and pointer cases: XShm draws no
menu.
2026-10-14 00:55:16 +02:00
Codex ee3a396e76 Generate validated DVD source file basenames 2026-10-14 00:54:51 +02:00
Codex eb52bacf6b Find exact DVD menu language and extension matches 2026-10-14 00:54:51 +02:00
Codex 3a21952543 Select borrowed DVD IFO or backup management input 2026-10-14 00:54:51 +02:00
Codex 536232a147 Resolve DVD menu IDs within explicit borrowed language units 2026-10-14 00:50:54 +02:00
Codex e5ea033290 Expose borrowed DVD menu language program-chain views 2026-10-14 00:50:53 +02:00
Codex 5b5aa804aa Read bounded borrowed DVD menu language tables 2026-10-14 00:50:53 +02:00
Codex 0ebb993bf8 gfx: release retired imports before pending image decodes
A dying surface may still have an image decode outstanding when its rejected hardware import completes. Release the unread hardware frame before deferring surface destruction to that decode.

Cover both completion orders for accepted, refused and dropped imports.
2026-10-14 00:50:41 +02:00
Codex 269710ff82 Resolve DVD chapters through borrowed VTS program chains 2026-10-14 00:42:53 +02:00
Codex 044b75d323 Expose DVD VMG titles and borrowed first-play program chains 2026-10-14 00:42:53 +02:00
Codex 7082353c11 Read borrowed DVD VMGI and VTSI management prefixes
Validate IFO signatures, declared sector extent and management-table pointers. Bound table views at the nearest later management table while keeping external VOB addresses separate from IFO-buffer offsets.
2026-10-14 00:42:53 +02:00
Codex 50f119f7f7 gfx: release rejected imports when retiring a surface
A queued hardware import can be refused after its surface is freed. Completion skips the surface owner's release callback in that case, so release the unread frame before destroying the surface.

Cover refused and dropped imports, preserve the release destination through completion, and ensure accepted imports are not abandoned.
2026-10-14 00:42:37 +02:00
Codex 10d1c3ccae Bound DVD PGC table entries to their borrowed record spans
Validate PGCIT last-byte and search-pointer bounds. Resolve unsorted entries using the nearest later record, allow shared offsets, and retain entry metadata without allocation or navigation side effects.
2026-10-14 00:35:25 +02:00
Codex 9ada6eacbf Expose DVD program cells, palettes and borrowed PGC commands
Return program cell ranges and validated VOB/cell sector metadata. Keep palette words in DVD YCrCb form and commands as borrowed byte spans. Revalidate serialized tables before access instead of trusting mutable view offsets.
2026-10-14 00:35:25 +02:00
Codex 16ac86c30e Read bounded borrowed DVD IFO program chains
Validate serialized PGC tables, command counts, program maps and non-overlapping spans without allocation. Add C89 native and MinGW/Wine sample coverage; source routing and navigation execution remain separate.
2026-10-14 00:35:25 +02:00
Codex aa5093730b Decode DVD subpicture rows into caller-owned index storage
Bound 2-bit RLE reads to the pixel region and decode directly without staging or RGB expansion. Cover all run encodings, fill-to-end, field cursors, byte alignment, output guards, maximum-size assembly and reset recovery.
2026-10-14 00:26:26 +02:00
Codex d6039005ba Parse bounded DVD subpicture display controls
Expose timed force/start/stop, palette, alpha, rectangle and field-offset events with explicit sequence bounds. Reject backward or overlapping control links and truncated arguments. Report unsupported regional colour commands without applying display state.
2026-10-14 00:26:26 +02:00
Codex 7e17c75212 Assemble DVD subpicture units with borrowed whole packets
Frame standard SPU units and retain the first-fragment timestamp. Borrow contiguous complete input and lazily allocate one bounded reusable buffer for fragments. Reset on malformed input and expose explicit consumed-prefix and view lifetime contracts.
2026-10-14 00:26:26 +02:00
LibretroAdmin 9fa57d84e5 3DS: the bottom screen's touches reach the core's pointer
While the bottom screen shows a core's screens, its touches are the
core's pointer at that point of the packed frame: the 3DS input driver
answers RETRO_DEVICE_POINTER through the views the bottom screen shows,
off them as offscreen. A DS core's touchscreen works on the 3DS's.

The lock-free map from a console's own display to the packed frame the
Wii U driver kept for the GamePad is video_views_touch_t now, shared by
both, with a unit test.
2026-10-14 00:23:05 +02:00
Codex 20f2f4d6ba Resolve DVD button neighbors and image-space hits
Validate group-local directional targets and provide bounded inclusive-rectangle hit testing without changing navigation state. Exercise borrowed commands through whole and every split PCI PES packet, and check inactive highlights in generated VOB input.
2026-10-14 00:17:15 +02:00
Codex 6c6660df08 Expose validated DVD button geometry and borrowed commands
Read group-local button rectangles and neighbor IDs with explicit wire loads. Borrow command bytes from the PCI packet and unpack selection/action palette indices and alpha without allocating or executing commands.
2026-10-14 00:17:15 +02:00
Codex 3a277c80ea Read bounded DVD PCI highlight metadata
Expose highlight timing, update mode, display groups and button counts without packed structs or allocation. Reject inconsistent counts and forced selections. Run the highlight tests in native sanitizer and MinGW/Wine sample lanes.
2026-10-14 00:17:15 +02:00
LibretroAdmin bf0dcd4067 Wii U: views
The Wii U driver draws a core's views on its two displays: the first
screen on the TV and the screens after it on the GamePad, which then
gets a colour buffer of its own. With the GamePad preferred, or a map of
one screen, the GamePad shows the TV's as before. Each view is a quad
sampling the frame's texture, laid out per display by the user's
aspect, scaling and rotation settings; a shader preset draws the frame
whole. Like the 3DS it shows one eye per display, so it lays the views
out flat and reports no stereo.

A core's pointer follows the GamePad's touches into its packed frame
through the views the GamePad shows, which the driver publishes behind
a sequence count for the input driver to read without a lock.
2026-10-14 00:08:12 +02:00
Codex 8b99b0c0c9 Build and exercise native DVD packet readers
Link the packet reader alongside the builtin program-stream demuxer. Run native sanitizer and MinGW/Wine tests in existing sample CI, with an optional generated VOB corpus exercising MPEG video, AC-3 and NAV packets. No external DVD or FFmpeg libraries are required by the reader.
2026-10-14 00:04:08 +02:00
Codex dde78c4518 Validate PES timestamps before exposing DVD packet clocks
Reject invalid MPEG-1/2 timestamp prefixes and marker bits. Exercise borrowed DVD packets, every split boundary, bytewise feeding, damaged timestamp recovery and PCI/DSI demuxing. Keep the absent-timestamp constant usable in strict C89 MinGW builds.
2026-10-14 00:04:08 +02:00
Codex c0cec342cc Read DVD LPCM configuration and NAV general information
Validate whole PCI/DSI payloads and expose endian-safe sector, timing and cell metadata without packed structs. Decode DVD-Video LPCM channel/rate/depth fields and reject reserved configurations. Navigation commands and menu rendering remain separate consumers.
2026-10-14 00:04:08 +02:00
Codex 7f250eef75 Add borrowed DVD private-stream packet views
Classify DVD-Video AC-3, DTS, LPCM and subpicture packets without allocation or payload copying. Keep substream identity and audio access-unit metadata, reject truncated headers, and document the borrowed lifetime.
2026-10-14 00:04:08 +02:00
LibretroAdmin c8418bd88e SDL 1.2: views
The SDL 1.2 driver draws a core's views into its window surface: each
view scaled straight out of the frame into its placement by a scaler
kept while its sizes stay, a placement the window cuts scaled aside and
the part shown copied. Anaglyph and interlaced draw both eyes into a
canvas whose halves the CPU blends into the window surface, by the
blend the GDI driver used, now video_views_blend_xrgb8888 for both,
with a unit test. Side by side and top-bottom draw the menu scaled into
each eye's area and messages in each. GPU screenshots read the window
surface, cut to the output size the driver now reports.

The views e2e cases run on SDL 1.2 under Xvfb in a CI job of their own,
with openbox to size sdl12-compat's fullscreen window. The pointer
cases are left out there: that window reports no pointer motion on
Xvfb.
2026-10-13 23:56:12 +02:00
Codex d43a0b3907 Allocate raw sector staging only when needed
Copy cached sectors directly to the destination and defer the single-sector fallback allocation until an unaligned or partial transport read needs it. Cached prefixes followed by aligned direct reads require no staging allocation.
2026-10-13 23:50:33 +02:00
Codex d059a33e68 Reject invalid disc dump sizes before opening output
Require positive whole-block DVD capacity and CD track sizes matching the captured TOC. Invalid or inconsistent input sizes must not publish empty or truncated images.
2026-10-13 23:50:33 +02:00
Codex 6eec0d8efa Publish dump cuesheets after all tracks complete
Keep the CUE in a sibling temporary file until every BIN has finalized. Failed or cancelled dumps retain the previous cuesheet and remove the temporary metadata. BIN publication remains per track.
2026-10-13 23:50:33 +02:00
Codex 34d62da0f4 record: drain the final partial silence debt
Encode pending silence shorter than an audio frame before flushing the encoder. Discarding that remainder truncates drop-mode recordings even when finalization succeeds.

Decode lossless recordings in the regression to verify the real audio prefix and all owed silence, with and without a whole encoder frame of debt.
2026-10-13 23:33:24 +02:00
Codex 43a7514aa8 Publish CD tracks only after successful finalization
Write each BIN track to a sibling temporary file. Preserve its previous destination on cancellation, read/write/close failure or failed publication, and discard the temporary output. Completed tracks remain independently published.
2026-10-13 23:32:29 +02:00
LibretroAdmin baa486ddfe samples: stop tracking built test binaries
font_lifecycle's chain, fallback, frame, sdl2 and vg, slang_cache's
mapped and read, and tasks/database's database_task are each lane's
own build output, so a make clean there deleted tracked files. They
leave the index and join the lane outputs already in .gitignore.
2026-10-13 23:31:49 +02:00
LibretroAdmin f3b85c26ae CI: retry the MSYS2 database sync in the cross-libc VFS lanes
The package update now runs inside the install step's retry loop
instead of in setup-msys2, so a mirror that stalls on msys.db is
retried rather than failing the UCRT64/MINGW64 lanes outright.
pacman waits out slow mirrors with --disable-download-timeout; the
job's timeout-minutes still bounds a mirror that never responds.
2026-10-13 23:31:37 +02:00
Codex 1f4d0e5609 Read aligned raw sectors directly within larger requests
Select the transport destination per sector so complete page-aligned sectors avoid staging copies even in multi-sector reads. Retain one bounded fallback buffer for partial or unaligned sectors.
2026-10-13 23:29:37 +02:00
Codex 3b86c0ddc1 Bound CD dump reads to the remaining track bytes
Do not request sectors past the current track boundary. Reject incomplete transfers rather than continuing a dump after a partial optical read.
2026-10-13 23:27:31 +02:00
LibretroAdmin cc4f80a2a8 tools/platform_stubs: the one tree of compile-only SDK stand-ins
The compile matrix's stubs move into tools/platform_stubs and the
matrix takes them from there. Where both trees had a header, the one
left declares what both sets of users need: ctr/3ds/os.h the clock
and the memory-region queries, ps3/ppu-lv2.h the scalar types and the
lv2syscall1 macro. The libnx stub carries the pad, Joy-Con and rumble
surface switch_joypad.c uses, and samples/input/switch_joypad builds
against it instead of a copy of its own.

Every compile-matrix lane gives the verdict it gave before.
2026-10-13 23:26:33 +02:00
Codex 0816a7b477 Reuse cached DVD sectors for complete reads
Avoid a repeated device command when a complete logical sector is already cached. Retain bounded direct transfers for cache misses and invalidate the cache after transport failure.
2026-10-13 23:24:18 +02:00
Codex a50b062769 Publish DVD images only after successful finalization
Write DVD dumps to a sibling temporary file and publish only after successful reads, writes, flush and handle closes. Cancelled or failed dumps discard the temporary image and preserve an existing destination.
2026-10-13 23:21:53 +02:00
Codex b29a6fc8dc Remove stale frees after consuming filestream close
The VFS close contract consumes the stream even on failure. Remove manual post-close frees in file helpers, Dingux and the CRC fixture so final I/O errors cannot cause double frees.

Report read, compare and write helper close failures; do not close consumed handles again on read cleanup. Failed atomic writes discard the temporary file and preserve the previous destination. Exercise normal/empty reads, native and callback ownership, and successful/failed atomic replacement under sanitizers and Windows.
2026-10-13 23:15:51 +02:00
LibretroAdmin 17a12db35d 3DS: views
The 3DS driver draws a core's views on its own two screens. The first
screen goes to the top screen: in 3D mode with the slider up its left
eye into the left framebuffer and its right eye into the right one,
which the parallax barrier shows each to its eye; otherwise the left
eye only. The screens after it go to the bottom screen while that is
on, which then shows the core's screen every frame instead of the
bottom menu, and its touches no longer act on the menu. With the
bottom screen off every screen goes to the top one. Each view is a
quad sampling the frame's texture, laid out per screen by the user's
aspect, scaling and rotation settings.

A driver that shows each eye on a display of its own says so with
GFX_CTX_FLAGS_VIDEO_VIEWS_OWN_EYES: the frontend then lays the views
out flat whatever the stereo mode, and reports stereo to the core
while the driver sets GFX_CTX_FLAGS_VIDEO_VIEWS_STEREO - on the 3DS,
while the slider is up in 3D mode, so a core can stop rendering the
second eye when it is down. video_driver_views_layout_flat lays a map
out for such a display, and video_views_select_screens takes some of a
map's screens as a map of their own, with a unit test.
2026-10-13 22:59:32 +02:00
Codex 932f34e9be record: handle mono and multichannel planar audio
Use the channel count when deinterleaving samples, retain the stereo path, and route mono through a duplicated pair for the stereo resampler. Fill FFmpeg frames with the packed plane layout produced by the helpers and propagate frame preparation errors.

Cover mono, stereo, three-channel and six-channel planar samples, allocation recovery, and actual AAC encoder input including partial final frames.
2026-10-13 22:59:18 +02:00
Codex f762d8418d Read complete raw CD cache hits without staging
Copy only the requested cached bytes into the caller destination when the read fits within a single valid cached sector. Avoid staging allocation, sector initialization and a redundant full-sector copy while preserving command MSF advancement and stale-media rejection.

Verify partial unaligned cache hits with staging allocation forced to fail. Cross-sector reads retain bounded staging and fetch the next sector, with existing Linux sanitizer and Windows error/recovery coverage.
2026-10-13 22:59:18 +02:00
Craig 12ca52c2d3 ci: use downloaded gradle for android jobs (#19820) 2026-10-13 22:59:00 +02:00
LibretroAdmin 440a43081e Switch: libnx is the only Switch build
Makefile.switch and every libtransistor path go: the Switch frontend,
input, joypad and audio drivers are libnx code only, the libtransistor
includes and timer/memory branches leave features_cpu.c and
mem_stats.c, the Switch video driver loses its disabled libtransistor
swizzling blit, and the frontend driver list has frontend_ctx_switch
once. Makefile.common's Switch target builds the libnx objects
unconditionally.

What the libnx build compiles is unchanged, as is every other platform:
resolving SWITCH, __SWITCH__ and HAVE_LIBNX in each touched file, as
defined and as undefined, leaves only dead or equivalent differences.
2026-10-13 22:52:26 +02:00
Codex 5d3f388270 Bound raw CD staging to one reusable sector
Reuse a single aligned 2352-byte buffer while issuing per-sector raw CD commands instead of allocating the entire request. Preserve direct aligned single-sector reads, cache maintenance, zero-filled staging, complete-or-error behavior and unchanged positions on failure.

Reject wrong raw-read directions and checked-length overflow before allocation. Verify bounded allocation, sector-offset reads, multi-sector recovery, and allocation-free direct reads with Linux sanitizers and Windows.
2026-10-13 22:48:28 +02:00
LibretroAdmin 03411438ce QNX input: gamepads on the port of their slot, and the trackpad all ways
The joypad driver reads a gamepad from the input driver's devices[]
slot its port names, and now everything agrees with that:

- a gamepad is announced on its slot's port, not on a count of pads
  seen so far that a hotplugged pad no longer matched;
- a gamepad unplugged lets its port go;
- a pad is there on a port whose slot holds a gamepad, and nowhere
  else; the joypad driver announces nothing of its own, where it
  announced all sixteen ports.

The trackpad compared the accumulator array with 0 rather than its
entries, so every swipe moved right or down; it now moves all four
ways, and reads nothing when no displacement came. Controller discovery
frees its device list on its second error path and declares before its
statements.

samples/input/qnx_pads builds both drivers in their unity order against
tools/qnx_standin, with libscreen's calls defined by the test, and
holds them to the above; the stand-in's screen_event_get_event()
returns the SDK's screen_event_t.
2026-10-13 22:41:34 +02:00
LibretroAdmin 05ad1ef062 GDI: views
The GDI driver draws a core's views into its window surface: each view
straight from the frame with StretchDIBits, clipped to its placement,
the DIB starting at the view's first row. Anaglyph and interlaced draw
both eyes into a canvas whose halves the CPU blends into the window,
anaglyph by the same Dubois matrices the other drivers use. The UI is
drawn once into a layer that stands in for the window surface, cleared
to transparent; its opaque fills are blended rather than filled, since
a fill leaves a DIB's alpha at zero, and AlphaBlend then places the
premultiplied layer in each eye. GPU screenshots read the window
surface the frame was composed in, or the frame's bitmap scaled into
the viewport.

The views e2e cases run on GDI under Wine.
2026-10-13 22:35:19 +02:00
LibretroAdmin 82d9cdbb32 3DS, Xbox, DOS joypad drivers: a pad is there only on a port that has one
- 3DS: the console's own controls are a pad on port 0, held or not. It
  answered from the buttons held, so an idle 3DS had no pad and while
  anything was held all sixteen ports had one.
- Original Xbox: query_pad, the name and the axis read are bounded by
  the four ports the console has; ports past them read past the
  driver's arrays.
- DOS: the driver reads no pad, so no port has one; it answered yes for
  all sixteen. Its button read fetched the keyboard state for nothing
  and is now the zero it always returned.

samples/input/ctr_joypad and samples/input/dos_joypad are new lanes,
and xdk_joypad checks the ports past the fourth. The 3DS stubs gain
libctru's HID and C-stick declarations, with types.h owning the shared
scalar names.
2026-10-13 22:34:39 +02:00
Codex 86d883dffb record: propagate audio buffer growth failures
Return conversion and planarization failures before encoding unfinished audio. Track float-buffer capacities independently so a retry completes outstanding allocations, and retain successful pair-buffer resizes if the next allocation fails.

Cover allocation failures and successful retries in integer, float, multichannel pair and planar audio paths.
2026-10-13 22:34:17 +02:00
LibretroAdmin 32c7fa1d69 D3D8: views
The D3D8 driver draws a core's views as the SDL drivers do: each view of
the frame is written straight into a texture of its own and drawn into
its placements, each quad cut to its placement since D3D8 has no
scissor; interlaced goes through a canvas whose halves fill the back
buffer's even and odd rows, and anaglyph, whose colours a fixed
pipeline can't make, draws the frame whole. The UI is drawn once into a
layer placed in each eye: a second texture stage premultiplies what it
draws, so the same blend keeps its alpha. GPU screenshots read the back
buffer through a copy into system memory after drawing the last frame
again, and a frame past the size the core declared grows the frame's
texture instead of running past it.

The views e2e cases run on D3D8 under Wine, but for anaglyph.
2026-10-13 22:14:40 +02:00
Codex 43fbb0fb62 Read aligned raw CD sectors directly into caller buffers
Avoid staging allocation, initialization and destination copying for page-aligned complete single-sector raw reads. Keep the sector-cache copy and preserve staging for partial, unaligned and multi-sector requests whose subsequent sectors lose page alignment.

Verify transport destination identity, cached reads without device commands, fallback paths and the existing failure/media-change contracts on Linux and Windows. Failed direct reads may modify the destination, as already documented.
2026-10-13 22:13:26 +02:00
LibretroAdmin 4e14425541 PS2: put a DualShock in analog mode when it connects
A DualShock 2 powers on in digital mode, so its sticks did nothing until
the player pressed Analog. Once per connection, when a pad is stable
with no request in flight and reports a digital mode, the driver asks
for DualShock mode if the pad's mode table offers one, unlocked so the
Analog button still switches it back. Pads already in analog mode, pads
with no DualShock mode and pads not yet stable are left alone, and none
is queried again until it reconnects.

The ps2_joypad lane checks the request is made once, unlocked, again
after a reconnect and only once a busy request completes, and never for
the pads above; the PS2 libpad stub gains padGetReqState(),
padInfoMode() and padSetMainMode().
2026-10-13 22:06:19 +02:00
Codex 5f9e893ff6 record: stop workers after scratch allocation failure
Mark recording failed, clear the worker's alive flag and wake producers when a required scratch buffer cannot be allocated. Free the remaining scratch before exiting so producers cannot remain blocked behind an undrained queue.

Cover startup audio and plain-ring video allocation failures, producer rejection, finalization failure and cleanup.
2026-10-13 22:04:44 +02:00
Codex 9ad928dc76 Reject unsupported macOS optical MODE SELECT commands
Route MODE SELECT through the existing ILLEGAL REQUEST path instead of returning synthetic success. Include ASC/ASCQ bytes in the fixed-format sense length for unsupported commands and failed raw reads.

Compile the actual macOS command mapper against an MMC stand-in on Linux and Windows. Exercise outgoing payload preservation, bounded sense output, supported MODE SENSE, transport failures and raw-read sense errors. This does not validate Apple SDK ABI or physical-drive behavior.
2026-10-13 21:55:04 +02:00
LibretroAdmin 074573d46c samples/input/ps2_joypad: build against the tree's PS2 stubs
The lane's own kernel.h, libpad.h and libmtap.h stand-ins move into
tools/platform_stubs/ps2: libpad.h and libmtap.h as new stubs with
ps2sdk's values and prototypes, and ALIGNED() into the existing
kernel.h stub, where ps2sdk defines it. The lane defines DIntr() and
EIntr(), which that stub declares.
2026-10-13 21:54:42 +02:00
LibretroAdmin 51980fe0a0 Console joypad drivers: a connected pad with nothing held is there
query_pad() answered from the buttons held, so a connected pad at rest
read as absent: keyboard-to-port assignment counted its port as having
no controller and the port notification left out its name. It now
answers from the pad's connection:

- PS2: the last read succeeded, or the pad is changing mode.
- PS3: the pad is connected, bounded by the pads the driver tracks
  (it was bounded by MAX_USERS and read past the array for ports 7-15).
- PS4: the last report came from a connected pad.
- Switch (libnx): padIsConnected().

The ps2_joypad, ps3_joypad, ps4_joypad and switch_joypad lanes check a
pad at rest is there, and gone once disconnected or not reporting.
2026-10-13 21:41:40 +02:00
Codex 3706511636 Report native close failures during disc dump finalization
Return stdio, descriptor and optical close errors while consuming native VFS handles. Release filestream wrappers after both native and callback close failures, as required by the VFS ownership contract.

Fail CD/DVD dumps on input or output close errors, clear ownership before close, and suppress successful completion on failure or cancellation. Cover native stdio/descriptor close errors, callback ownership, and cuesheet, track, image and cancellation paths under sanitizers and Windows.
2026-10-13 21:33:28 +02:00
Codex cc3270cb82 record: report drain scratch allocation failures
Mark finalization failed when required audio or plain-ring video scratch cannot be allocated, while retaining safe draining and cleanup.

Inject both failures after the encoder has joined and verify failure reporting and output closure alongside successful recording.
2026-10-13 21:31:21 +02:00
LibretroAdmin e1bfd2f98d SDL3: views
The SDL3 driver draws a core's views as the SDL2 driver does: each view
of the frame is written straight into a texture of its own, RGB565
widened on the way in, and drawn into its placements clipped to each;
interlaced goes through a canvas whose halves fill the window's even
and odd rows, the UI is drawn once into a layer placed in each eye with
a premultiplied blend, and anaglyph, whose colours no blend makes,
draws the frame whole. The viewport readback draws the views again. A
desktop fullscreen window is made at the desktop's size, so it fills
the screen without a window manager to grow it.

The views e2e cases run on SDL3 in the Linux CI, against an SDL3 built
from a pinned release with its renderer.
2026-10-13 21:22:31 +02:00
Codex 052dd4b03c record: retain encoder failures through finalization
Remember audio/video codec and packet-write failures while the encoder owns the recorder. Read the result after joining and draining so a later successful trailer cannot hide an incomplete recording.

Cover send, receive, packet-write, and final-drain failures alongside normal recording and cleanup.
2026-10-13 21:20:06 +02:00
Codex 60f3e2c3dc Reject optical reads after observed media changes
Latch medium-change, reset and no-medium sense conditions on CD/DVD handles with bound geometry. Invalidate the raw-sector cache and reject further data reads until reopen, rather than retrying onto another medium. Metadata-only probes remain reusable.

Cover fixed and descriptor sense on raw CD and cooked DVD reads, unchanged positions, readiness observations, stale cached reads, reopen recovery and ordinary recoverable errors. This is per-handle observation, not cross-handle generation tracking or detection of an unobserved media swap.
2026-10-13 21:19:20 +02:00
Codex e83c1fe1c7 ci: strip Linux sample executable suffix placeholders
Remove the literal EXE placeholder when reading sample targets so the CD-ROM and CDFS tests match the run allowlist.
2026-10-13 21:01:50 +02:00
Codex 56e998d51b record: report finalization I/O failures
Check trailer, buffered output, and close errors while always closing the file. Propagate failed finalization through frontend teardown and skip history insertion for incomplete recordings.

Cover failing output writes, close failures, sticky AVIO errors, frontend cleanup, and successful completion.
2026-10-13 20:48:20 +02:00
LibretroAdmin 78ca161a02 SDL2: views
The SDL2 driver draws a core's views as gl1 does: each view of the
frame is written straight into a texture of its own, RGB565 widened on
the way in as every other driver widens it, and drawn into its
placements with SDL_RenderCopyEx, clipped to each. Interlaced draws
both eyes into a canvas whose halves go to the window's even and odd
rows; the UI is drawn once into a layer placed in each eye with a
premultiplied blend. Anaglyph, whose colours mix channels no blend can,
draws the frame whole, as a renderer without render targets does with
the canvas and UI layer. The viewport readback draws the views again.
A desktop fullscreen window is made at the desktop's size, so its
drawable matches what SDL renders to without a window manager to grow
it.

The views e2e cases run on SDL2 in the Linux CI, but for anaglyph.
2026-10-13 20:44:28 +02:00
Codex e23b4bcb5b Reject changed CD geometry between dump tracks
Compare each reopened track handle with the original dump TOC before creating its output. Fail on missing metadata or changed track geometry rather than combining tracks from differing layouts. This does not detect replacement media with identical geometry.

Test all geometry fields, absent metadata, track count changes, and changes to current or other tracks. Keep unrelated metadata refreshes and device timeout preferences separate from geometry.
2026-10-13 20:37:13 +02:00
Davey Hughes d67324c157 CI: faster slow steps, and ccache that hits (#19819)
* CI: run the qemu sinc kernel tests side by side

The four NEON sinc tests ran one after another under qemu-user on the
aarch64, aarch64+crc and armv7 legs, five to six minutes a leg, with
the runner's other CPUs idle. Each is one single-threaded qemu
process, so they now run at once, and each one's output is printed
when it is done. A failing test still fails the step.

On CI the step went from 300s to 102s on aarch64, 329s to 169s on
aarch64+crc and 318s to 249s on armv7, where one test alone sets most
of its length.

* tools/tsan-samples.sh: build each suite with every CPU

The suites were built one compile at a time. Built with -j on four
CPUs, the 68 directories in the list took 29s instead of 77s, and none
that builds without -j failed with it. The suites themselves still run
one at a time, so on CI the whole step went from 737s to 622s.

* views e2e: under Wine, each worker gets a prefix of its own

Cases run side by side in one prefix share its wineserver and moved
each other's pointer, so the Wine cases ran one at a time. Each worker
now takes a copy of the prepared prefix, and Wine runs four cases at
once like Xvfb.

On CI the D3D11 and D3D12 cases went from 1457s to 479s, all 120
passing; the pointer cases passed in three more runs on four CPUs.

* CI: one gl harness job per build, and one job for the wait checkers

The gl harness ran as 13 jobs, one per driver, API and sanitizer, but
the build depends only on the API and the sanitizer: each job set up a
runner, installed the same packages and built the same tree as two
others before its one or two minutes of harness. It now runs as 6
jobs, one per build, each running the harness on every driver that
build serves. Every driver runs even when one before it fails.

The three wait and teardown checkers, each under a minute of work,
ran as three jobs; they now share one, every checker running even
when one before it fails.

On CI these 16 jobs became 7, and 27.1 runner-minutes became 24.5.

* CI: MSYS2 installs Qt's base package only

The MSYS2 lanes installed about 40 Qt 6 modules, though configure
reports Qt support disabled on all four of them, so none of it is
built.

qt6-base still brings in the libraries configure does find (FFmpeg,
SDL2, FreeType, fontconfig, libass, glslang, SPIRV-Tools, OpenSSL).
The MinGW packages fetched drop from 198 (597MiB) to 152 (319MiB). On
CI the install steps of the four lanes went from 772s to 523s, and
configure found the same libraries on each.

* CI: sanitizer and debug builds of the tree use -g1

The samples moved to -g1 in 21635dd1, but every DEBUG=1 build of the
tree still took the Makefile's -O0 -g: the gl and Vulkan harness
sanitizer builds, pace-shadow, the TSan harness, the autoconf
sanitizer matrix and the C89 debug build. A sanitizer report needs
only line tables, which -g1 keeps.

Those jobs now set CFLAGS and CXXFLAGS to -O0 -g1 in the environment.
Where configure sees them it writes them into config.mk, and where
only make does, the Makefile's CFLAGS ?= takes them; either way they
replace DEBUG=1's -O0 -g and the Makefile's own flags are still
added, which they would not be from make's command line. The
threaded-video harness takes its compile line from the tree's, so its
run step sets the same. The one-off -O1 -g test compiles in the
workflows and the companion test scripts use -g1 as well. The Makefile
default is unchanged for local debug builds.

The ASan+UBSan DEBUG=1 build, from an empty cache on four CPUs here:
ccache 83MB -> 55MB, objects 530MB -> 424MB, 84s -> 80s. The gl
harness passed on that build, and an ASan report from a -g1 binary
still names the file and line.

* CI: print ccache's statistics in every job that keeps a cache

Only 7 of the 34 jobs that restore a ccache printed its statistics, so
for the rest a cold or missed cache was invisible. Each now prints
ccache -sv before saving, including the direct and preprocessed hit
counts, and a failure to print does not fail the job.

* Makefile, Makefile.webos: GIT_VERSION for version_git.c only

-DGIT_VERSION=<hash> went on every file's command line. ccache's direct
mode hashes the command line, so each new commit missed it for every
file, and CI's restored caches only ever hit after running the
preprocessor again: about 2.5% of hits were direct. Only version_git.c
reads the macro; the rest test HAVE_GIT_VERSION, which stays global.
Makefile.webos has its own compile rules and gets the same change.

On CI, with the cache of the commit before, the ASan+UBSan build had
418 of 418 hits direct.

* CI: the cross-arch ccache keys name their architecture

The retro-atomic-cross and crc32-cross legs shared one cache key per
commit, so only one leg's cache was saved ("Unable to reserve cache
... another job may be creating this cache") and the next run's legs
all restored whichever architecture won. Each leg now saves its own.

* CI: prune superseded ccache entries

Master pushes save each job's ccache under a key ending in the commit,
and only the newest per job is ever restored. Upstream's 10GB store
held 256 entries: 61 newest-per-job (4.24GB), 195 superseded (6.46GB),
and 2.3GB of other caches. Least recently used went first, so the
newest cache of a workflow that had not run for about four hours was
gone by its next run, which then built cold.

A workflow on each master push and hourly deletes every ccache entry
keyed on a commit that a newer one of the same job on the same branch
replaces. Other caches are left to GitHub's own eviction.

* CI: MXE builds keep a ccache

The two MXE cross builds compiled RetroArch from nothing on every run.
Their images ship ccache, so they now restore and save one like the
Linux jobs, with the compilers wrapped at configure time.

On CI, with the cache of the commit before, the compile went from 154s
to 18s on x64 and 166s to 11s on i686. A run with no cache at all is
slower than before, about 250s, as is any first build through ccache.

* CI: console builds keep a ccache

Switch, PS Vita, Wii, GameCube, DOS and the macOS PowerPC cross builds
compiled every file on every run. Their images run actions/cache but
ship no ccache, so the job installs it, and a small script reads CC
and CXX from the platform Makefile with the job's own variables and
runs make with both wrapped. The PowerPC legs each keep a cache of
their own. As in the Linux jobs, the cache is capped at 250MB, its
statistics are zeroed after the restore, and it is saved on master
pushes only. Installing ccache is retried and best effort: without it
the script runs plain make. The PS Vita and Wii Salamander builds,
which run first, are left as they were.

On CI, with the cache of the commit before:

  PS Vita   120s -> 4s     Switch           84s -> 2s
  Wii        50s -> 9s     GameCube         44s -> 9s
  DOS        49s -> 4s     macOS PowerPC   116s -> 2s

With no cache at all most took 5-30s longer than before; PS Vita took
148s longer.

PS2, RS90 and RetroFW are left out: their compiles did not go through
ccache this way. Miyoo is too: it hit every compile but went only from
50s to 38s, about what installing ccache costs.

* CI: webOS builds keep a ccache

The webOS job builds three configurations with make clean between
them, each from nothing. It now sets up ccache and wraps the compilers
like the console jobs.

On CI, with the cache of the commit before and before Makefile.webos
kept the commit hash to version_git.c, the three compiles went from
110s to 53s.
2026-10-13 20:10:01 +02:00
LibretroAdmin 4709c07f39 D3D9 HLSL: views through presets
A preset draws a core's views on the HLSL driver as on D3D10: each view
through a chain of its own, a copy of the preset's with its own frame
textures, render targets and history, into which each view of the frame
is written straight out of it, and whose last pass draws the view's
placements. The chain's programs, declarations and lookup textures are
the preset's. Screenshots of a viewport below the window's top read the
rows it covers.

The views e2e cases run the Cg presets pass, pass_viewport, pass_one,
prev and prev_one on D3D9 HLSL, pixel for pixel.
2026-10-13 18:03:56 +02:00
Codex 2d160f4607 Give optical handles independent TOC geometry
Own validated track geometry per CUE/BIN handle instead of publishing a global mutable TOC. Keep borrowed menu metadata alive and snapshot dump geometry before closing the source. Separate readiness and media-profile probes from track access with metadata-only device handles.

Exercise independent drive geometry, handle cleanup, invalid paths, metadata probes and dump snapshot ownership. Generate sample dependency files to rebuild consumers when the optical handle layout changes.
2026-10-13 18:03:07 +02:00
LibretroAdmin 6de0fb0aad Console joypad drivers: a pad that stops reporting lets go of its sticks
Every console joypad driver now takes stick values only from a report
that carries them, and a pad that is gone, unreadable, or of a kind the
driver does not read holds nothing and reads centred:

- PSP: a failed read is skipped as on Vita.
- PS3: a disconnected pad's sticks are centred. Rumble keeps each
  pad's motor values and sends both, so setting one leaves the other
  as it was; the large motor scales across its 0-255 range.
- PS4: a report is read only from a pad reporting itself connected; the
  handle is kept for when it reports again.
- GameCube/Wii (libogc): sticks are centred before each pad is read, so
  a pad pulled out, a bare Wiimote or a Nunchuk in place of a Classic
  leaves nothing held.
- Switch: a lone Joy-Con centres the right stick.
- Wii U Wiimote: a device type the driver does not read holds nothing.
- Original Xbox: the device handle is kept across polls, a pad that
  cannot be read or is removed holds nothing, handles are closed before
  the state is cleared, and the X button case is terminated.

The PSP read macro evaluates its port argument, and the touched files
are MSVC C89 clean (INLINE, no trailing enum comma, unsigned for uint).

samples/input psp_pad_read, ps3_joypad, ps4_joypad, gx_pad_sticks,
switch_joypad, wiiu_kpad and xdk_joypad build each driver against
stand-ins for its SDK and hold it to the above: a stick held when the
pad goes away, changes extension or fails a read reads centred, and
PS3 rumble sets one motor without disturbing the other. The PS3
and PS4 stand-ins are added to tools/platform_stubs.
2026-10-13 17:00:12 +02:00
Codex 2c6b61e9fb runloop: tick RUN_FRAMES only after advancing the core 2026-10-13 16:57:29 +02:00
LibretroAdmin 92d69e6025 D3D9: views
The HLSL and Cg drivers draw a core's views as gl1 does, through code
they share in gfx/common/d3d9_views.h: each view is drawn straight from
the frame's texture by a shader model 2 pixel shader that stops
filtering at its edge, anaglyph and interlaced blend a canvas of both
eyes, and the UI is drawn once into a layer placed in each eye. A
preset draws the frame whole. A frame past the size the core declared
grows the frame's textures instead of running past them.

The views e2e cases run on D3D9 HLSL under Wine.
2026-10-13 16:53:26 +02:00
Codex be85ba3e7a Fix CDFS read errors and large-disc offsets
Publish only complete sectors, preserve the cursor and cache on failed or short reads, and return valid prefixes before later failures. Reject failed directory reads and keep file positions, sizes and sector offsets in 64 bits with checked end-relative seeks.

Clamp signed read failures at the hashing callbacks. Cover fault recovery and sparse 8 GiB cooked/raw layouts on Linux and 32/64-bit Windows, retaining direct full-sector reads and the existing pure CDFS tests.
2026-10-13 16:36:32 +02:00
RetroArch Audit bf4c20dd0f UWP: synchronize dispatched display result publication
Use retro_atomic release/acquire completion for width, height, refresh-rate and DPI dispatcher requests. Keep UI event dispatch and display cache behavior unchanged. This patch addresses publication only; dispatcher failure and apartment-aware parking require separate changes.
2026-10-13 16:27:32 +02:00
RetroArch Audit d24d4a151c UWP: publish async completion through retro_atomic
Replace volatile completion flags with release/acquire publication so result and exception writes are visible to the waiter. Initialize before installing the handler, including inline completion. Preserve dispatcher pumping and existing cancellation behavior; this does not change the wait strategy.
2026-10-13 16:27:32 +02:00
Codex 3acf7fd7db d3d11: retain frame owners after decoder device removal
Treat UINT64_MAX fence values as device removal rather than completed reads. Keep closed decoders alive until outstanding frames are abandoned, and return a fatal output error without creating another lease.

Cover removal during decode, failed output signaling, and delayed frame abandonment.
2026-10-13 16:13:48 +02:00
Codex 4d2ec37bf1 Dump physical DVDs as ISO images
Select the cooked image path for DVD media and stream bounded chunks through one reusable aligned buffer. Preserve CD cuesheet/track dumping while checking complete writes and flushes, accounting progress after successful writes, honoring cancellation and clearing closed handles. Report task failures separately from successful completion.

Add real state-machine/file-I/O tests for DVD images, multi-track CDs, short writes, read/open/allocation failures and cancellation on Linux and Windows. No DVD navigation libraries are added.
2026-10-13 16:12:33 +02:00
Codex 9a35d82ca4 Fix optical read errors and CD track geometry
Stop raw reads at the first failed sector, preserve cache hits and outgoing command payloads, and propagate Windows sense data. Normalize track-info LBAs to the absolute MSF origin and validate complete bounded TOCs before constructing cuesheets. Derive BIN seeks from one checked byte position and report read failures through VFS.

Keep legitimate short metadata responses distinct from incomplete sector transfers. Add simulated Linux/Windows regressions for failed sectors, malformed and duplicate TOCs, track sizes, end-relative seeks, cache hits and command direction. Expose current media profiles and cooked-image URI construction for DVD consumers.
2026-10-13 16:12:29 +02:00
Codex bffe92cae8 video: retry hardware decoder backpressure without waiting
Poll decoder and output availability instead of sleeping or waiting for a held texture. Preserve pending pictures, input packets and the presentation clock until a later player poll can proceed.

Cover busy decode and output retries, inline and threaded playback, and native D3D11 texture saturation, recovery and cancellation.
2026-10-13 15:52:24 +02:00
Codex 45cede3586 Add cooked DVD block access to optical VFS
Expose read-only 2048-byte disc images on Linux and Windows alongside the existing CD cue/bin paths. Probe capacity per handle, keep sizes and seeks in 64 bits, reject short transfers, and transfer aligned blocks directly to the caller with a bounded fallback for unaligned reads.

Add simulated-drive coverage for SCSI commands, large images, VFS opening, seeking, EOF and transport failures on Linux and Windows. UDF, disc-menu routing, DVD navigation and macOS cooked reads remain separate work.
2026-10-13 15:43:58 +02:00
LibretroAdmin c16c0a4b1b gl1: views
The legacy GL driver now honours a core's views. Each view of a
software frame is written straight into its own texture and drawn
with a viewport and scissor per placement; anaglyph and interlaced
presets compose through an offscreen canvas and a small GLSL blend
pass where FBOs and shaders are available, and the menu, widgets and
messages can be drawn once per eye through a premultiplied UI layer.
Without FBOs the driver falls back to drawing the frame whole, and
scRGB output keeps views off. Non-views paths are unchanged.

The e2e harness runs the gl1 presets with exact hardware checks.
2026-10-13 15:43:19 +02:00
Codex ebcc7a6c38 Fix builtin movie resume and remove MP4 frame copies
Save the display slot, playback clock and audio cursor so restoring a movie resumes with the same video and PCM. Preserve legacy states and build the core without audio codecs.

Render MP4 frames directly into the core framebuffer using the existing stream output contract, and keep audio scratch buffers within the console stack budget.

Add a real-core host and Linux sanitizer/Windows CI covering playback, seek, reset, resume, end-of-stream, reload and feature-disabled builds without FFmpeg linkage.
2026-10-13 15:23:56 +02:00
LibretroAdmin 1826e059e9 PS2: read the sticks only from a pad reporting in an analog mode
padRead() hands over the whole 32-byte report whatever mode the pad is
in. In digital mode (0x41) the stick bytes hold whatever padman's
receive buffer last held, and other pad types carry other controls
there, so the driver now reads the sticks only from analog (0x5x) and
DualShock (0x7x) reports and centres them otherwise. Every poll writes
the pad's buttons and sticks whole, so a pad that is gone, busy or
unreadable holds nothing and reads centred.

samples/input/ps2_joypad builds the driver against stand-ins for
libpad and libmtap and holds it to this: analog and pressure modes
read both sticks, a digital report with stick bytes left over or junk
reads centred and moves nothing on the RetroPad, other types read
centred, and a pad that drops out lets go.
2026-10-13 15:22:26 +02:00
LibretroAdmin 390bad3e5d Metal: a software frame's views written straight into their textures
A preset's views have each view of the frame written straight out of
it into the history its chain reads, where the frame used to be
uploaded whole and each view then copied out of it on the GPU: blitted
out of the frame memory buffer the frame is in, or written from the
core's frame. A 16-bit view goes into a staging texture of its size and
is converted from there, as the frame would have been. The frame itself
is written only when it is drawn whole or straight from the frame.
2026-10-13 14:59:21 +02:00
LibretroAdmin d74ab2927b D3D10: views
D3D10 draws a core's views as D3D11 does: the stock chain draws each
straight from the frame, and a preset draws each through a chain of its
own, a copy of the preset's with render targets, feedback and history of
its own, into which each view of the frame is written straight out of
it; anaglyph and interlaced blend a canvas of both eyes, and the UI is
drawn once into a layer placed in each eye. The preset's state moves
into a chain for that, and a last pass with feedback draws into a target
to keep it. GPU screenshots and recording read the back buffer through a
ring of staging copies, as D3D11's do.

The views e2e cases run on D3D10 under Wine, but for the rect presets,
whose gathers shader model 4 can't compile.
2026-10-13 14:50:51 +02:00
LibretroAdmin f152156178 views: a software frame's views written straight into their textures
A preset's views that can't be read where they lie in the frame have
each view of a software frame written straight out of it into the
texture its chain reads, where the frame used to be uploaded whole and
each view then copied out of it on the GPU: gl2 and glcore upload each
view with the unpack row length, D3D11 maps each view's texture, D3D12
and Vulkan copy each view out of the upload, staging or lent buffer the
frame is in. The frame itself is written only when it is drawn whole,
straight from the frame or read in place. On gl2 a dupe reads each
view's last texture again instead of copying it anew.

The views e2e cases check that a software frame's views go through a
preset that way on gl, glcore, D3D11 and D3D12; Vulkan's do from a
staging buffer, as lavapipe's RGB565 frames are.
2026-10-13 14:50:48 +02:00
Codex f6c8cabb7f input: release injected controls on command teardown
Clear command-held buttons and axes after destroying every command interface so disabling or reconfiguring commands cannot leave controls held. Add a regression using the real input driver to check destruction, generation invalidation and input release.
2026-10-13 14:45:59 +02:00
LibretroAdmin ceedc745a4 CI: link with lld on the ubuntu runners
lld ships with the runner image under a versioned name. A step after
checkout in each ubuntu job (83 jobs; container jobs are left alone)
runs .github/workflows/scripts/use-lld.sh, which finds it and puts a
small "ld" first on PATH that the host gcc then links with. Cross
compilers keep their own prefixed linkers and clang its own.

GNU ld still does the links lld cannot stand in for: any link with
--wrap (lld also wraps calls inside the defining object, which the
wrapping samples do not expect), and gcc -flto objects, which lld
cannot read - those fail under lld and are run again with GNU ld.
No lld on the runner leaves the job on GNU ld.

RetroArch's link here: 1.6s with GNU ld, 0.9-1.2s with lld. The
samples that wrap, use -flto, --gc-sections or -Map build and pass
their checks through it.
2026-10-13 12:44:01 +02:00
Codex 2b50e6642f Document rh264 licenses and regenerate CABAC data from BSD sources
License original decoder code under MIT and retain Cisco BSD-2-Clause notices for OpenH264-derived data. Replace the FFmpeg-sourced extended initialization block with reproducible tables generated solely from pinned OpenH264 data and audited H.264 context mappings. All 4096 CABAC pairs remain unchanged.
2026-10-13 12:23:06 +02:00
Codex 8c18ac18ae Use defined wrapping arithmetic in volatile integer atomics
Compute fallback add/subtract in unsigned arithmetic, matching the wrapping behavior of native atomic backends. The H.264 volatile-backend sanitizer test exposed signed overflow in its publication-delay seed.
2026-10-13 12:23:06 +02:00
LibretroAdmin 21635dd171 samples: build with -g1 instead of -g
The sample and test builds used full debug info. -g1 keeps what
sanitizer reports and backtraces need - function names, files and line
numbers - and drops the variable and type information nobody reads in
CI. Objects get about 30% smaller (rh265.o under ASan+UBSan: 5.1MB to
3.7MB), so the ccache caches hold more of each job's build and are
quicker to restore and save.
2026-10-13 11:50:11 +02:00
LibretroAdmin 4fe15c815b CI: keep ccache caches from master pushes only
Each of 34 jobs saved a ccache of up to 500MB-1GB under a key with the
commit in it, on every run, pull request pushes included. GitHub keeps
10GB of caches per repository, so the jobs pushed each other's caches
out and runs started cold.

The jobs now restore with actions/cache/restore and save with
actions/cache/save only on a successful push to master; pull requests
restore master's cache and save nothing. The caps drop to 250MB (and
500MB for the one 1GB job), which with the -g1 objects still holds a
job's build.
2026-10-13 11:50:03 +02:00
LibretroAdmin 97e8330d8f CI: build the samples with every CPU the runner has
About 270 sample builds in the Linux workflows ran as
"make clean all ..." with no -j, one compile at a time on a runner
with several CPUs. They now clean first and build with -j$(nproc):

  make clean ARGS && make -j$(nproc) all ARGS

Clean and build stay separate: "make -j clean all" can run the two at
once. Steps that run tests (check targets) are unchanged, and only
bash steps of jobs on ubuntu runners are touched.

On a 2-CPU box one sample (samples/tasks/database) went from 36.3s to
18.6s. Every changed build that also builds without -j was run here
with -j8; none failed.
2026-10-13 10:57:09 +02:00
LibretroAdmin 69331a439c gl: views through a preset that reads earlier frames or feedback
A preset with PrevTexture or a feedback pass made gl draw the frame
whole. Each view now keeps its own: a ring of its copies as deep as the
preset reads back, with the earlier frames it hands the passes pushed
after every draw as the frame's are, and a feedback target per view
that trades places with the chain's after the view is drawn, as the
frame's does. Nothing changes for a frame drawn without views.

The views e2e cases run the gl presets that read Prev6Texture, in one
pass and two, and a feedback pass that keeps the brighter of its input
and what it drew before; each fails when a view reads the whole
frame's history or the other view's feedback.
2026-10-13 10:56:06 +02:00
LibretroAdmin 93fb836c6d CI: run the C checkers, drop the Python ones
The workflows call tools/check.sh tools/<name>.c where they ran
python3 tools/<name>.py, and the scripts are removed. Comments and
allow-list headers that named the scripts name the C files.

Not converted, on purpose: stack_budget.py spends its time in gcc, not
in Python; ci_local.py and compile_commands.py are developer tools
whose CI step tests the tools themselves; settings_check.py is a
developer tool; the samples' e2e drivers run RetroArch.
2026-10-13 10:23:47 +02:00
LibretroAdmin 28e15c72fc json2h: check the generated tables with the C checkers
The format-mismatch row filter and the packed-table verification now
call tools/msg_hash_format_check.c and tools/msg_hash_packed_check.c
through tools/check.sh instead of importing the Python modules, which
are going away. Same tables, same rows dropped.
2026-10-13 10:23:47 +02:00
LibretroAdmin b366054913 tools: C ports of the remaining Python CI checkers
Every Python checker still run by a workflow, rewritten in C89 on
tools/check_common.h and run through tools/check.sh, which builds each
one once and reuses the binary:

  command_table_check        overlay_setter_bounds
  companion_parity           overlay_texture_ownership
  ctx_presentable_check      removed_member_check
  d3d12_idle_wait_check      rgui_pixfmt_check
  gl_present_vsynced_check   sample_objdir_check
  gl_ring_fence_check        settings_capacity_check
  input_driver_table_check   surface_requirements_check
  input_video_separation_check  texture_image_init_check
  msg_hash_format_check      vfs_backend_parity
  msg_hash_packed_check      video_thread_state_check
  vulkan_idle_wait_check     vulkan_teardown_order_check

Each gives the same output and exit status as the script it replaces,
on the tree and on its self-test; each was also run against the
script on a few hundred generated inputs with no difference. Where the
script iterated a Python set, the C reports in sorted order.

msg_hash_format_check gains --rows FILE, the row filter intl/json2h.py
needs. rgui_pixfmt_check's harness moves out of a string into
tools/rgui_pixfmt_harness.inc, which includes the lifted block.

check_common.h gains ck_capture() and ck_sb_shq() to run a command and
read its output, ck_set_free(), ck_repr(), ck_blank_code() and
ck_def_name().
2026-10-13 10:23:47 +02:00
github-actions f002f8e03e Fetch translations from Crowdin 2026-10-13 00:29:18 +00:00
LibretroAdmin f96539d0b7 CI: the removed-member lane clones the history without its blobs
It needs both ends of a range, so it fetched all of the history: 730 MB
for one git diff. With filter: blob:none the commits and trees come
(57 MB) and the diff fetches the blobs it reads.
2026-10-13 00:38:53 +02:00
LibretroAdmin bf8ea24785 samples/gfx/gl2_pbo_readback: link video_views.c
gl2.c calls video_views_fit_frame(), _first_drawn() and _visible()
since the driver draws views, and the sample did not link them.
2026-10-13 00:38:49 +02:00
LibretroAdmin 1b86833a76 command: SET_INPUT and RELEASE_INPUT ask the input driver by name
They took the input state to write what a command holds, two more
takes than command.c is allowed. input_driver_command_hold(),
_release() and _held() own it now. input_state_grab_check passes
again.
2026-10-13 00:38:46 +02:00
LibretroAdmin 208534c4fc gl2: the passes' texture settings are kept, not read on the video thread
A frame past the textures makes them and the render chain again from
gl2_frame(), and gl2_create_fbo_textures() read video_smooth and
video_force_srgb_disable from the settings there, which the menu may
be writing. init and set_shader take them once; the remake uses what
they took. tools/video_thread_state_check.py passes again.
2026-10-13 00:38:42 +02:00
LibretroAdmin 725addee99 tools: the four slowest tree checkers in C
overlap_copy_check, settings_def_guard_check, input_state_grab_check
and extern_c_prototype_check took 38 s, 21 s, 10 s and 9 s of Python
on the tree. They are C89 now, built and run by tools/check.sh, and
take 0.9 s, under 0.01 s, 0.5 s and 0.3 s. Same options, same output,
same self-tests; tools/check_common.h holds what they share.

Text is read as bytes: a word character is [A-Za-z0-9_]. The guard
checker reports a guard it cannot read where the script would have
evaluated call or subscript syntax as Python.
2026-10-13 00:38:39 +02:00
libretroadmin d2f24d32b9 gl: views through a preset
With a GLSL or Cg preset each view is copied out of the frame into a
texture of its own, top row first as a software frame, and drawn
through the preset's passes, one view after another in its
framebuffers, the last pass into each of the view's placements. A
preset that keeps frames from one draw to the next - frame history or
feedback - or takes mipmaps on its first pass still draws the frame
whole.

The end to end cases run on gl through passthrough presets of one
pass, and of two with the first at the view's size or its placement's.
2026-10-13 00:29:18 +02:00
libretroadmin 1af52a81f5 views e2e: run cases side by side
On Xvfb the cases run four at a time (RA_E2E_JOBS), each on its own
server, port and directory, their results printed in order. Most of a
case is waiting on the menu or the screenshot, so more than the cores
still gains: on two cores the gl cases take half the time, glcore and
Vulkan together ten minutes. Under gamescope, whose screenshot names
one display, and Wine, whose prefix is shared, they run one at a time.
2026-10-13 00:29:15 +02:00
libretroadmin 714db64c8b MCP server: stdio transport with log notifications
--mcp-stdio serves MCP on stdin and stdout as newline-delimited
JSON-RPC, for a client that starts RetroArch itself; no token is asked.
It takes the stdout descriptor for the protocol at option parsing and
points stdout at stderr, so RetroArch's log and a core's prints never
reach the client's stream. Reads and writes never wait: a line is
handled once whole, answers are queued and written as the pipe takes
them, and both outlive the server across a content load. It offers
logging: after logging/setLevel, new lines from the GET_LOG ring at
that level and above go out as notifications/message from the poll, so
the threads that log do no more than before. Closing stdin quits.
2026-10-12 23:34:51 +02:00
libretroadmin ae32b07cfe command: GET_LOG, from a lock-free ring of the last lines logged
RARCH_LOG_V copies each formatted line into a ring of the last 256
lines once it is armed, which the MCP server's start or the first
GET_LOG does; unarmed, a log line costs one relaxed load more. Any
thread may log: a writer claims a line number, writes the slot as
atomic words between a busy mark and its stamp, and the reader keeps a
line only when the stamp matches before and after the copy, so a
reader never waits and never sees a torn line. GET_LOG answers the
lines since the one asked for, then NEXT; lines overwritten before they
were read are counted as SKIPPED. Desktop builds with the MCP server
only; samples/runloop/log_ring writes from four threads while reading,
under ASan and TSan.
2026-10-12 23:34:47 +02:00
libretroadmin 0a4aff89ef command: RUN_FRAMES
RUN_FRAMES pauses the content and runs it a given number of frames
through frame advance, answering once the last has run, so a client can
step the game and look between steps. While it runs a frame-work bit
makes each iteration a frame advance; after each core_run the command
counts the frame off, and closing the content answers it with an
error. With the bit clear the run loop does nothing new.
2026-10-12 23:34:45 +02:00
libretroadmin a808962621 command: SET_INPUT and RELEASE_INPUT
SET_INPUT holds a RetroPad button or stick axis for a player, read by
the core over the player's own controller through the input state
wrapper, until it is set again or RELEASE_INPUT lets it go; replays
record it like any other input. A single flag tells the wrapper there
is anything to add, so with nothing held a query costs one test.
Achievements hardcore mode refuses it.
2026-10-12 23:34:42 +02:00
libretroadmin 2250fc9c80 gl: make the frame textures again for a frame past them
A software frame larger than the geometry the core declared was
uploaded past the textures' edge, which fills none of them, and the
screen stayed black. The textures, and the buffers sized from them,
are made again at the next power of two that holds the frame, as
set_frame_format already does for a larger scale, and as glcore grows
its own. The crop map's end to end cases run on gl again.
2026-10-12 23:25:18 +02:00
libretroadmin 3fd92a76cf gl: views
The driver draws a core's views through video_views_draw.h as glcore
does: straight from the frame, through a canvas for anaglyph and
interlaced, with the UI drawn once into a layer composited into each
eye, where alpha keeps coverage. The programs are GLSL 1.10 or ES 1.00,
1.50 with a core context, made on first use. With a shader preset, HDR
output, or while a headset shows the frame, the frame is drawn whole.

The end to end views cases run on gl, a hardware core through a
compatibility context; a frame past the declared maximum, which gl
draws black, is left out. CI runs them with glcore and Vulkan.
2026-10-12 23:10:55 +02:00
libretroadmin 77fb3f46af views e2e: shoot a case once its status is final
Each case waited a fixed five seconds before its screenshot and a
second after the PNG appeared. It now waits for the core to log the
views status the case expects, then half a second (two under Wine) for
the frames that take up its layout, and reads the PNG as soon as it is
written to its end. The GPU memory case still samples for five
seconds. On a two-core runner with llvmpipe a driver's cases run in a
quarter of the time.
2026-10-12 23:10:52 +02:00
LibretroAdmin 408d63b522 retro_eventcount: spin before the futex, as the Win32 backend does
A Linux waiter went to futex_wait as soon as its re-check found the
epoch unchanged, so a wake that was microseconds away still cost the
park and the kernel wake. The futex backend now spins on the epoch
for the same calibrated budget the Win32 backend spends on its flag
word - 3 us of measured relax, on more than one processor, the same
RETRO_EVENTCOUNT_SPIN_US / RETRO_EVENTCOUNT_SPIN overrides - before
committing to the kernel.

retro_eventcount_bench on a two-core Linux host: pingpong 22 us/round
to 0.30 us; broadcast to four waiters unchanged within noise, those
being oversubscribed there.
2026-10-12 22:52:27 +02:00
libretroadmin 4cedf14c38 Add tools/compile_commands.py
It writes compile_commands.json for a configured tree from the commands
make would run, so clangd and other code navigation see each file's
defines and include paths. Its self-test runs in the CI local runner
workflow.
2026-10-12 22:28:27 +02:00
libretroadmin d69939f054 command: GET_CORE_OPTION, SET_CORE_OPTION and GET_MEMORY_MAP
GET_CORE_OPTION lists the running core's options as key, value and
description, and with a key adds the values that option takes;
SET_CORE_OPTION sets one to one of those values, changing nothing when
it already has it. GET_MEMORY_MAP lists the core's memory map regions
with the addresses READ_CORE_MEMORY takes, their masks, kind, access,
byte order and address space, so a client can find memory without
guessing. All three are tools on the MCP server, described there.
2026-10-12 22:25:21 +02:00
libretroadmin 69cac4a865 D3D12: retire views' chains behind the fence
A change in view count lets the chains past the new count go through
the retired list instead of draining the queue, so it no longer stalls
the frame. A chain that can't be remembered stays in its slot until the
preset goes. The preset is only freed after a drain, so the chains
still on the retired list are let go before it.
2026-10-12 22:24:11 +02:00
Nacho Sanchez Gines 61774503ca Add new geartowns core (#19812) 2026-10-12 22:21:34 +02:00
libretroadmin fa8ef856c9 d3d_feedback: follow the D3D11/D3D12 filter chains
The harness extracts the chain functions: init_render_targets taking the
chain, the swap from the per-pass loop of chain_frame, and D3D12's clear
and transitions, which now run inside its init. The model types gain the
chain, its passes and D3D12's queue command list.
2026-10-12 22:16:45 +02:00
libretroadmin 4f3fed830d DXGI bridge: build with Visual C++ before 2015
The SDKs the MSVC 2005 and 2010 griffin builds use have no dxgi1_5.h
or d3d12.h. On those compilers the bridge is a set of stubs whose
create fails, so the WGL and Vulkan contexts take native presentation.

A check in the Visual C++ 6 stand-in workflow builds that branch with
the newer DXGI and D3D12 headers poisoned and links it with the WGL
presenter.
2026-10-12 22:09:11 +02:00
LibretroAdmin 421a4279ac compile-matrix: a failing tree check is reported, not a silent exit
The platform makefile and console object checks captured their status
after a plain command substitution, which under set -e ends the script
on the first failure before the status is read: no FAIL line, exit 1,
and every lane after it - the video drivers, the gates, the unity
build - never run. A host missing one package the checks compile
against made the whole matrix look run when two thirds of it had not.
The status is now taken in the same list as the substitution, so the
check prints its FAIL and the lanes after it run.
2026-10-12 22:00:26 +02:00
LibretroAdmin 05487f6794 vulkan: show what the present waits for ahead of the display
Under the threaded wrapper a hardware core's frame has its fence waited
out before the present, and the queue submit waits for the queue lock
the core's thread may hold. Neither shows on the statistics overlay,
where the Swap Wait line counts only the present itself, so whether
the wait before it costs anything cannot be read off.

The driver now counts both in cycles, scaled by the clock reads the
present already takes, and reports them through a new optional poke
entry, get_last_present_sync. The wrapper averages them as it does the
present wait and the overlay shows them on a Present line under Swap
Wait.
2026-10-12 21:48:59 +02:00
LibretroAdmin df01f12756 retro_spsc: keep the cursor groups a full line apart at any placement
The pads after head and tail were a line less the cursor pair, which
puts the two groups 64 bytes apart: aligned, they sit on separate
lines, but the struct carries no alignment and an embedder on the heap
starts wherever malloc puts it, so a start late enough in a line had
the producer's cached_tail sharing a line with the consumer's tail. Each
pad is now a full line, so the next group - and whatever the embedder
declares after the struct - begins a line past the previous group's
last byte, whatever the offset.

retro_spsc_test checks the layout.
2026-10-12 21:21:05 +02:00
LibretroAdmin f076e4f75b gl2: fence each PBO readback, map a slot only once it has signalled
The recording readback mapped a PBO four frames after its glReadPixels
on the assumption that the GPU had finished with it, which turns the
map into a wait on a loaded GPU. A fence is placed after each readback
now and a slot is read only once its fence reports complete, polled
without a timeout; a slot still pending is left for the next readback.
Contexts without sync objects keep the age-based read.

samples/gfx/gl2_pbo_readback compiles the driver against a scripted
GL and checks the ring: no map on a pending fence, the mapped frame
once it signals, pending fences replaced on reissue, every fence
deleted at teardown.
2026-10-12 21:21:05 +02:00
libretroadmin 0b42f588c1 Add tools/ci_local.py and per-directory agent notes
tools/ci_local.py runs a workflow's jobs locally with the steps, shell,
environment, matrix and container CI uses, and "which" lists the jobs
that cover given files. Needs nothing beyond the Python standard
library; the selftest, run in CI, checks every workflow reads as PyYAML
reads it and resolves in a dry run.

AGENTS.md points to CODING-GUIDELINES and ci_local.py and states the
threading rules; libretro-common and gfx carry their own notes.
2026-10-12 21:14:41 +02:00
libretroadmin 354f388680 Metal: views
The driver draws a core's views through video_views_draw.h as the D3D
drivers do: straight from the frame, or with a preset each through a
copy of the main chain - its pipelines and lookup textures referenced,
its semantics pointed at its own values and textures, its own constant
buffers, render targets and history, the preset's parameters shared.
FrameView's chain is drawn by methods that take an engine_t. Anaglyph
and interlaced blend both eyes through a canvas, and the UI is drawn
once into a layer composited into each eye; the blended BGRA8 UI
pipelines keep coverage in alpha for it, which the drawable does not
show. The pipelines are compiled from source on first use. With HDR
output the frame is drawn whole.
2026-10-12 21:05:37 +02:00
LibretroAdmin 0ef4194293 vulkan: record readback clears only what the frame leaves uncovered
vulkan_record_read() zeroed the whole BGR24 output before converting
the readback into it, a pass the conversion then overwrote in full
whenever the recorded frame covers the viewport. Only the rows and
columns the frame does not reach are cleared now, and the conversion
goes through pixconv's SIMD BGR24 converters, written bottom-up with a
negative output stride.

pixconv_test gains the bottom-up case for both converters.
2026-10-12 20:45:08 +02:00
LibretroAdmin 837473acf7 record/ffmpeg: video ring mirrored, one publish per frame, read in place
The video ring is mirrored, so a frame is one contiguous span across
the wrap: the push packs its rows into one reservation and publishes
once, and the encoder side scales straight out of the ring instead of
copying the frame into a scratch buffer first. Where a ring cannot be
mirrored the push publishes per contiguous span and the encoder side
keeps its scratch copy for the frames that straddle the wrap.

ffmpeg_pipeline_test pushes every other frame with a padded pitch and
gains a PLAIN_RING build that takes the unmirrored path.
2026-10-12 20:45:08 +02:00
LibretroAdmin c0db9af777 camera/pipewire: one dequeue attempt per poll, return every buffer
preprocess_image() looped until pw_stream_dequeue_buffer() handed it a
buffer, which parks the main thread until the camera's next frame
lands - a full camera period on every poll a slower camera has nothing
for, and indefinitely once the stream stalls. A poll now makes one
attempt and reports no frame when the queue is empty.

A buffer without CPU-visible data was also dropped without being
queued back, so each one shrank the pool the stream has to work with.
Every dequeued buffer now goes back on every exit.

samples/camera/pipewire_poll drives the driver's poll against a
scripted stream with the pw_stream calls link-wrapped and checks each
case; it runs in the samples/cores workflow next to the ffmpeg camera
lane.
2026-10-12 20:45:08 +02:00
libretroadmin ea863ca5d4 Remove Travis CI leftovers and move driver notes to docs/
Travis CI no longer builds RetroArch; .travis.yml, the encrypted deploy key
and the Metal deploy script go. The OMAP, Exynos and Mali fbdev driver notes
move from the root into docs/, and gmon.out from profiled builds is ignored.
2026-10-12 20:42:49 +02:00
Codex 6ed73ba72b Initialize Wine views tests with a working display
Run wineboot under Xvfb and wait for the Wine server to exit before launching the first views case. Initialization without a display can leave the first D3D11 window creation failing while subsequent cases pass. Bound setup and propagate its errors instead of ignoring them.
2026-10-12 20:38:12 +02:00
libretroadmin 84638cec2a Should have never been added 2026-10-12 20:36:21 +02:00
libretroadmin 3aed2beb52 D3D12: views through a preset
The preset's chain moves into d3d12_filter_chain_t, drawn by functions
that take it. Each view gets a copy of the main chain: its pipes
referenced and its lookup textures read where the main chain keeps
them, its semantics pointed at its own values and textures, its own
constant buffers, descriptor tables, render target views, render
targets and history, the preset's parameters shared, so nothing is
compiled or read again. A view is read where it lies in a hardware
frame when every pass can and there is no history, else copied out.
Views no longer fall back to the whole frame with a preset; the end to
end preset cases run on d3d12.
2026-10-12 20:23:45 +02:00
Codex 7f16ec2760 CI: wait for Wine views startup before timing cases
Wait for the core views status before starting the settle and screenshot windows under Wine. Cold launches can otherwise consume the command window before the core starts. Bound the wait and stop on process exit, retaining the existing output checks.

Preserve per-case logs when the Wine views workflow fails.
2026-10-12 19:41:30 +02:00
libretroadmin a9c5823eba D3D11: views through a preset
The preset's chain moves into d3d11_chain_t, drawn by functions that
take it. Each view gets a copy of the main chain: its shaders and
lookup textures referenced, its semantics pointed at its own values and
textures, its own render targets and history, the preset's parameters
shared, so nothing is compiled or read again. A view is read where it
lies in a hardware frame when every pass can and there is no history,
else copied out. Views no longer fall back to the whole frame with a
preset; the end to end preset cases run on d3d11.
2026-10-12 19:35:59 +02:00
libretroadmin c5674a72bc D3D11, D3D12: ask for the waitable object only of an IDXGISwapChain2
The swap chain is held as an IDXGISwapChain4 but made as an
IDXGISwapChain; where it is not a 2 (Windows 7, Wine) the call read
past its vtable and crashed. Without one the frame doesn't wait. The
views end to end harness runs with waitable swap chains on again.
2026-10-12 19:12:41 +02:00
Codex 894aae9f35 menu: expose threaded video pipeline on Apple platforms
Restore the threaded video option in the video settings menu on Apple platforms and rename its English label to Threaded Video Pipeline.
2026-10-12 19:08:25 +02:00
libretroadmin 838e23591c CI: reject a directive inside a macro's arguments
MSVC's preprocessor takes it as tokens; GCC accepts it. The clang lanes
that compile every driver now make -Wembedded-directive an error.
2026-10-12 18:49:24 +02:00
libretroadmin c70d117497 D3D11, D3D12: no directive inside VIDEO_VIEWS_UI_WANTED's arguments
MSVC's traditional preprocessor takes it as tokens, which broke the
Windows build, as in b298330 for gl3 and Vulkan.
2026-10-12 18:36:15 +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
libretroadmin 204f03cfec 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.
2026-10-12 18:25:46 +02:00
libretroadmin c05e75d71d 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.
2026-10-12 18:25:43 +02:00
libretroadmin e8d51d4362 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.
2026-10-12 18:25:40 +02:00
Codex 1334026922 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.
2026-10-12 18:19:30 +02:00
Codex c67a4ca1e5 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.
2026-10-12 18:19:30 +02:00
Codex fcdbd28ee3 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.
2026-10-12 16:58:01 +02:00
Codex f52e55e56d 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.
2026-10-12 16:51:50 +02:00
Codex 00e507fc7b 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.
2026-10-12 16:49:25 +02:00
Codex b298330d7b 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.
2026-10-12 15:59:19 +02:00
libretroadmin d4090fd145 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).
2026-10-12 15:34:51 +02:00
libretroadmin 5995c1f760 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.
2026-10-12 15:34:25 +02:00
libretroadmin a28f9ac0f3 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.
2026-10-12 15:34:22 +02:00
Codex ee3903fcd4 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.
2026-10-12 15:32:39 +02:00
Codex c1e363e36d 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.
2026-10-12 15:30:02 +02:00
Codex a845a8790c 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.
2026-10-12 15:09:42 +02:00
Codex 3972121f7c 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.
2026-10-12 15:09:42 +02:00
Codex 9c542e898b CI: install MinGW and Wine for cloudsync commit test
The content-thread commit check cross-compiles with x86_64-w64-mingw32-gcc and runs the executable under Wine. Include both dependencies in the existing package installation and presence check.
2026-10-12 14:42:08 +02:00
libretroadmin 32840318ac GL/GLES: frame memory and the framebuffer loan on GLES 3
A GLES 3 build (Android among them) now keeps the threaded wrapper's
frames in persistently mapped unpack buffers and lends the core its
framebuffer from them, as the desktop GL drivers do, where the
context is GLES 3 with EXT_buffer_storage. Elsewhere it copies as
before.

- gl_common: HAVE_GL_FRAME_MEM covers desktop GL and GLES 3; texture
  lending stays desktop-only, since it sizes the lent texture from
  its level parameters (GLES 3.1). The support check reads the GLES
  version and the extension, and storage goes through
  glBufferStorageEXT there.
- gl2: the GLES row-length upload reads a frame in frame memory out
  of its buffer, with the unpack alignment taken from the pitch.
  Frame memory and the loan are offered only where the upload reads
  the frame as it is - not where the CPU converts or repacks it
  first, or hands it to an EGL image.
- gl3: the same paths under the new gate.
- Harness: a GL driver with no frame memory, or no loan, fails the
  frame memory and lent-window lanes instead of skipping them;
  texture lending and the planar readback are expected only where
  they are built.
- CI: the GL harness job also runs GLES 3 builds on llvmpipe (gl and
  glcore; release, ASan+UBSan, TSan).
2026-10-12 13:57:54 +02:00
Codex 2c88e1ba33 Metal: initialize the viewport without resizing restored windows
Seed the viewport from the drawable size after applying a macOS video mode. Restored windows can keep their geometry and skip the resize callback that normally initializes rendering.

Install the resize delegate only after the context and viewport are ready, so callbacks cannot dereference an uninitialized viewport.
2026-10-12 13:56:04 +02:00
Codex 5fbf22321f android: resolve startup storage access on return from Settings
Launch Settings in its own task and check storage access after pause/resume, preserving pending requests across activity recreation. Settings redirects can otherwise release startup before the user responds.

Keep the API 16 input listener behind a version guard for Android 2.x, skip unsupported startup work, and reuse core scan resources and preferences rather than fetching them for every directory and library.
2026-10-12 13:43:49 +02:00
Codex 57c9edf689 MCP: bound parameter names in error diagnostics
Limit names to 96 bytes so both 128-byte diagnostics keep their closing punctuation and terminator, and GCC format-truncation builds remain clean.
2026-10-12 13:28:56 +02:00
Codex 7e16cc1ac6 WinRaw: publish mouse modes and button reports atomically
Read one published device mode per report. Keep callback button state in an atomic word and expose only the polling thread snapshot to the frontend. Publish a report together with compare-exchange so a concurrent focus clear cannot be overwritten by an earlier read.

Cover forced publication interleavings and concurrent report/focus stress in a portable fixture, and verify swapped buttons and focus transitions through the real driver. Retain hotplug state and add Linux sanitizer and MinGW coverage.
2026-10-12 13:28:56 +02:00
Codex 7361e44c2d Cloud sync: serialize save mutations with content ownership
Run the budgeted sync handler on the content thread while blocking network calls retain their worker. This keeps ownership checks, backups, replacements and deletions ordered with savefile lifetime changes.

Keep a local save in place until its download is complete. If the completion cannot be handed to the handler, discard the temporary file and carry the manifests forward instead of committing on the callback thread.

Exercise threaded and unthreaded queues, both replacement modes, ownership acquired during transfer, backup contents and failed completion allocation.
2026-10-12 13:28:56 +02:00
GavinDarkgliderandLibretroAdmin 264323607b Lakka / Lakka Switch: reboot-to-payload and "Install Update" reboot label (#19755)
* Lakka: Show Install Update on reboot entry, if update exists.

* Lakka: Switch: Add reboot2payload driver

* Lakka: no directory scan or stat behind the Reboot entry's value

The menu asks for an entry's value every frame. Whether an update is
staged is looked up when the main menu is built and before a reboot,
and whether the kernel has pmc_r2p once. The kernel command line is
read whole: Hekate's pmc_r2p.* values come at its end.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-12 13:02:37 +02:00
libretroadmin 920eaf90b4 D3D12: H.264 previews decoded on the GPU
The D3D11 video backend's frames, opened on the D3D12 device through
the same NT handles: the shared NV12 texture as two plane views, the
fences as ID3D12Fences. Each frame converts in a command list of its
own, executed at once on the driver's queue behind the decoder's fence
and signalling the reader's, so nothing waits on the CPU; with all four
lists still in flight the frame is dropped. They signal a fence of
their own, as a signal on the queue fence between a frame's recording
and its execution would take the value that frame's resources wait for.

The backend starts on the D3D12 driver's adapter and loads d3d11.dll
itself. The GPU oracle in samples/gfx/d3d11_video_decode now reads
every stream through D3D12 as well, where there is a device.
2026-10-12 12:47:59 +02:00
Codex 2f22d06ba7 Remove duplicate Metal command-buffer stub declarations 2026-10-12 12:38:31 +02:00
Codex 47811aa6e2 Reject DXGI timing samples older than their frame submission
Native NVIDIA/G-SYNC testing returned matching presentation IDs and refresh counts with QPC samples from before the frame was submitted. Treating those samples as completed-frame timestamps prevented the threaded latency statistics from updating.

Record the synchronized present's submission clock and reject earlier samples. Retain estimated timing when DXGI feedback cannot describe that frame, and preserve the interval-zero path.
2026-10-12 12:33:02 +02:00
LibretroAdmin 1dfca13a17 d3d: presets that leave the first pass's filter unspecified read in place
slang_process() rewrites such a preset for the filter the driver says
an unspecified one samples with: d3d11's smooth setting, which it keeps
following live - toggling it builds the passes that read the frame
again, in place, parameters and all - and d3d12's linear, which its
passes always use. d3d.sh checks both ways of smooth.
2026-10-12 12:31:00 +02:00
libretroadmin 2db49be6b4 D3D11: H.264 previews decoded on the GPU
rh264's hardware backend on D3D11 video (DXVA), as the Vulkan one is
on Vulkan Video. Each decoder has a device of its own on the driver's
adapter, so the driver's device stays on its own thread. A picture to
be shown is copied into a shared NV12 texture behind a shared fence;
the driver opens both by their NT handles, waits on the GPU, converts
with the planar compute shader and raises the texture's own fence.
Needs Windows 10 1703 and NV12 sharing; anything less, or a stream
the GPU will not take, decodes on the CPU as before.

The DXVA buffers are written byte by byte (gfx/common/dxva_h264.h), so
no dxva.h is needed. samples/gfx/d3d11_video_decode checks them against
one, runs the backend on a D3D11 that records what it is told, and has
an oracle that compares a GPU's frames with software's.
2026-10-12 12:28:53 +02:00
libretroadmin 001f98e773 Metal stubs: MTLCommandBuffer's status
frameMemReady reads it, and the compile matrix's Metal lane failed for want of it.
2026-10-12 12:28:49 +02:00
libretroadmin e758c18d33 D3D11: texture uploads never wait for the staging texture
A frame or texture update copied through a staging texture mapped it
with a plain write map, which waits while the GPU still copies from it
- a frame drawn through a preset with history, the menu texture, new
font glyphs. The map asks not to wait, and when the staging texture is
still busy the pixels go in through UpdateSubresource, which never
waits. Only a frame the copy converts to another format still waits for
its staging texture.
2026-10-12 12:28:14 +02:00
libretroadmin 9168dc8186 Metal stubs: MTLCommandBuffer status
The compile matrix's Metal stand-ins declare the command buffer's
status, which the frame memory checks for a completed blit.
2026-10-12 12:28:07 +02:00
Codex f0b6392479 Stabilize threaded frame-memory regression measurements
Drain warmup and measured frames before inspecting frame-memory counters, so GPU scheduling cannot replace pushes and fail the storage checks.

Build the Metal CI harness for the runner OS to match its Homebrew libraries and avoid deployment-target linker warnings.
2026-10-12 12:12:26 +02:00
libretroadmin 6e4cc989c4 Metal: frame memory for threaded video, and a lend that never waits
The Metal driver hands the threaded wrapper its frame buffers
(frame_mem_alloc/ready/free) as shared-storage buffers, and copies a
frame pushed from one into the frame texture with a buffer-to-texture
blit at its offset and pitch: the video thread no longer copies it
with replaceRegion. A buffer is ready once the command buffer of the
last blit out of it reports completed, and is let go at once, the
command buffer holding it while it reads.

The software framebuffer lent to a core with threaded video off comes
from the same memory: three buffers at the tight pitch, never the one
the last frame was pushed from nor one a blit still reads. The lend no
longer waits for the previous frame's blit to complete, and no longer
needs a linear texture over the buffer, so widths whose pitch a linear
texture would not take are lent too. metal_texture_update's lent
framebuffer lane checks the buffer blit, odd widths included, and the
harness's frame memory lane runs on Metal.
2026-10-12 11:34:19 +02:00
Codex 92c746e196 Fix regression sample Vulkan includes and compiler warnings
Resolve bundled Vulkan video headers in the memory-type and OpenXR haptics harnesses. Mark unused DSP parameters, gate translation state by its consumers, and remove an unused RawInput local.

Reserve checksum space in test payloads and bound RSound control messages so their headers describe the transmitted payload.
2026-10-12 11:32:29 +02:00
Codex 05358e15df Use confirmed DXGI frame timing for threaded latency statistics
Match DXGI feedback to the most recent synchronized present and accept its QPC timestamp only when it names that frame's vblank. Retain estimated timing for missing, stale or disjoint feedback and for unsynchronized presents.

Account completed frames at their display timestamp while keeping the next-repeat deadline in the future. Honor completion feedback even with no measured wait, and cover delayed next-buffer acquisition in the shipping threaded harness.
2026-10-12 11:31:24 +02:00
libretroadmin 3662677a02 GL: lend the software framebuffer with threaded video off
The gl and glcore drivers lend a core its framebuffer
(GET_CURRENT_SOFTWARE_FRAMEBUFFER) from the frame memory the threaded
wrapper uses, three buffers at the frame's size: never the one the
last frame came from, which the cached frame names, nor one an upload
still reads, and never waited for. The frame the core rendered there
is uploaded out of it by the GL. gl_sw_fb in gl_common.c serves both;
gl2 lends where its upload reads the frame as it is.

A persistent mapping may be write-combined, so a core that asks to
read its framebuffer is not lent one by GL, and under the wrapper gets
a cached host buffer whenever the driver's frame memory is not cached.
A slot that fell back to host memory goes back into the driver's as
soon as a buffer there is free. The harness core can ask for write
access alone, which the lent-window lane now does on GL, and the frame
memory lane holds a reading core to cached loans.
2026-10-12 11:22:11 +02:00
LibretroAdmin b0e2b556bd slang_rect: linear filtering with border wrap read in place
The edge texels are sampled as with clamp to edge and scaled by the
share of the filter's footprint inside the rectangle, the border's
transparent black being the rest. Inside, the share is exactly 1 and
the read bit-exact; the outermost texels' blend with the border uses
the shader's weight rather than the sampler's quantised one, so it
holds to within a step of 8 bits, which slang_rect_test, run.py and
d3d.sh check it to.
2026-10-12 11:06:29 +02:00
libretroadmin f73b0abcab Win32: open the log console behind RetroArch
Turning logging on opened the console window over RetroArch and took
the focus. It now goes to the bottom of the window order and the focus
goes back to RetroArch's window.
2026-10-12 11:01:31 +02:00
libretroadmin 8268e480a3 Win32: no QuickEdit in the log console RetroArch opens
A selection in that window holds every write to it, so the next log
line stalled whichever thread made it, the main thread included, until
the selection was let go. The console RetroArch attaches to instead
keeps its own settings; Edit > Mark still selects in ours.
2026-10-12 10:47:21 +02:00
Codex 4a6ed10d3c Avoid waitable pacing for unsynchronized DXGI presentation
Pass the effective presentation interval into buffer acquisition for Vulkan and WGL. Skip the frame latency object wait for interval zero when tearing is supported, while retaining synchronized and compatibility pacing. Keep the GPU ownership fences and queue ordering intact.
2026-10-12 10:38:32 +02:00
libretroadmin 815654eda5 ratrac3p: the rest of the second channel's tone coding
Found on a stream real encoders made, where half the frames used them:

- tone counts copied from the first channel or coded as deltas to it;
- tones past the first channel's, their frequencies from its last one;
- amplitudes copied, or relative to the first channel's tone nearest
  in frequency (within 7, else the one in the same place);
- a band coded with no tones still sets the envelope of its region;
- slave gain locations in mode 1 where the level rises: the master's,
  or ascending from the last;
- a block with nothing coded has no scale factor, table or spectrum
  fields.

That stream now decodes within 4 LSB of a reference decoder throughout.
ratrac3p_test gains synthetic frames for each.
2026-10-12 10:31:06 +02:00
libretroadmin 53c98983c3 raac: report the bytes a unit takes
raac_last_size, for a caller handed a buffer that may hold more than
one access unit: the bytes up to and including the END element.

raac_size_test checks it over hand-built units with more after them.
2026-10-12 10:31:06 +02:00
libretroadmin 262c2e4c60 rmp3: decode one frame at a time
rmp3dec_init and rmp3dec_decode_s16, for a caller that frames the
stream itself: the first frame in its buffer, junk before the sync
skipped and counted, the bit reservoir kept between calls.

rmp3_frame_test holds it against the pull API, sample for sample.
2026-10-12 10:31:06 +02:00
libretroadmin 9582343337 Vulkan: H.264 previews decoded on the GPU
The device is made with H.264 decode queues and transfer queues where
the GPU has them (VK_KHR_video_decode_h264). vulkan_video_decode is
rh264's backend on them: a video session per stream, DPB and output
coinciding, each picture shown copied out into an image the driver
imports. Thumbnails take these frames on an imported surface.

Settings > Video > Output > Hardware Video Decode (Auto/Off).
2026-10-12 10:31:06 +02:00
libretroadmin a602dbeef8 video_player: frames decoded on the GPU
An MP4 H.264 stream takes rh264's hardware backend when asked
(image_transfer_anim_stream_set_hw), and a sink that takes GPU frames
(start_hw) gets each one as video_player_frame_t::hw. A frame no one
reads is given back to its decoder.
2026-10-12 10:31:06 +02:00
libretroadmin 8c47362d76 rh264: hardware decoding backend hook
rh264 keeps the sequencing - parameter sets, picture order counts,
reference marking, output order - and hands each picture to a backend
(rh264_set_hw_backend) as its slice NAL units, its slot and the
references it may name. Progressive 8-bit 4:2:0 Baseline/Main/High
only; anything else, or a refusing backend, decodes in software.
Frames come out as video_hw_frame_t, a GPU frame with timeline
semaphores.

rh264_hw_test checks the sequencing against a backend that checks
it, and the output order against the MP4's presentation times.
2026-10-12 10:31:05 +02:00
libretroadmin 1723e48ace gfx/include/vulkan: update headers to 1.3.275, add vk_video
The video decode extensions (VK_KHR_video_queue, video_decode_queue,
video_decode_h264) and their codec std headers. C++ comments turned
into C89 ones.
2026-10-12 10:24:08 +02:00
libretroadmin 13d8c632a0 GL: threaded video frame buffers in driver memory
The gl and glcore drivers hand the threaded wrapper its frame buffers
(frame_mem_alloc/ready/free) as pixel unpack buffers, persistently
mapped where the GL has buffer storage, as the lent texture slots are.
A frame pushed from one, copied in by the wrapper or rendered there by
the core, is uploaded out of it by the GL, the upload queued instead
of the frame being copied on the video thread first. A buffer is ready
once the fence behind its last upload has signalled; gl_frame_mem in
gl_common.c serves both drivers. A hardware core keeps its own frames,
and GLES and a GL without buffer storage keep the host buffers.

The harness's frame memory lane runs on gl and glcore as well.
2026-10-12 10:19:23 +02:00
github-actions 1815302e85 Fetch translations from Crowdin 2026-10-12 00:31:33 +00:00
Codex 1144e01d29 Limit HDR and Vulkan refresh changes to video and input
Keep the video rebuild required by HDR formats and the Windows Vulkan fullscreen refresh path without restarting unrelated drivers. Preserve the existing menu command dispatch and dependent HDR row refresh.
2026-10-12 00:48:23 +02:00
libretroadmin 9ac78e087c gfx_surface: imported frames, converted on the GPU (Vulkan)
A third kind of surface beside streaming and still ones: imported. Its
frames already live on the driver's device - a hardware decoder's
output - and never reach the CPU. Two new pokes carry it:
get_device names the device a producer has to make its frames on, and
import_texture converts a frame into the surface's texture. Under the
thread wrapper the import is a node (VIDEO_THREAD_ASYNC_IMPORT) that
the video thread runs, as an update is.

The Vulkan driver takes NV12 images: the planes are copied on the GPU
into a device-local buffer and converted by the yuv420_to_rgba8888
compute shader the planar uploads already use, into one persistent
texture. The copy waits for the producer's timeline semaphore and
signals the producer's other one when it is done, so nothing waits on
the CPU and a decoded frame goes back to its pool as soon as it is
read rather than when it leaves the screen. The device is made with
VK_KHR_timeline_semaphore where the GPU has it; a device without it,
or one a core made, imports nothing.

The threaded-video harness gains an import lane on lavapipe with a
host-written NV12 image and host-signalled timelines standing in for a
decoder: direct and threaded, one texture for the stream, every frame
released, the validation layer clean, and the texels matching the
CPU's conversion.
2026-10-12 00:47:49 +02:00
Codex 872ddc9297 gfx: bound length scans for clipped ticker labels
Count unselected labels only until they exceed the field width. Selected tickers retain the full character count for scrolling. Preserve UTF-8 continuation-byte counting and cover fitting and clipped multibyte labels.
2026-10-12 00:40:27 +02:00
Codex 8e94ac9e2e Scope reservation and surface recovery restarts to video and input
Controller reservation changes need input rediscovery, and surface recovery needs video reconstruction. Preserve unrelated drivers and core metadata by using the coupled video/input restart scope for both paths.
2026-10-12 00:37:48 +02:00
Codex 14c3fb1126 encoding: bound UTF-8 copy scans by destination capacity
Stop scanning once the output buffer is full instead of walking the rest of a long source. Preserve whole-glyph truncation and add boundary coverage for mixed UTF-8 text.
2026-10-12 00:34:45 +02:00
Codex a810576b97 string: carry wide wrapping widths across space breaks
Keep the measured word tail after a space break instead of copying and decoding it again. Preserve the rewind paths for stale spaces and short remaining input. Add a scan-count regression for accented word tails.
2026-10-12 00:30:45 +02:00
Codex b7af5bcb2d Limit format and font fallbacks to video and input
Keep the in-place paths first. When a frame format or driver-private OSD font needs a rebuild, preserve audio and core metadata by restarting only video and its coupled input driver.
2026-10-12 00:27:08 +02:00
libretroadmin 796d957788 D3D12: threaded video frame buffers in driver memory
The D3D12 driver hands the threaded wrapper its frame buffers
(frame_mem_alloc/ready/free) from the WRITE_BACK heap its lent
framebuffers use, and copies a frame pushed from one into the frame
texture on the GPU, a window into one included: the video thread no
longer copies it through the texture's upload buffer. A buffer is
ready once the fence of the frame that last copied out of it has
passed, and is let go behind that fence.

frame_mem_alloc also names the row pitch multiple the driver reads
frame memory at in place: 256 bytes for D3D12, a word for Vulkan. The
wrapper pads the rows of a frame it copies in to it, which costs the
copy nothing; a loan stays at the core's own pitch, and one the driver
cannot read in place is copied as before. The harness's frame memory
lane runs on D3D12 too.
2026-10-12 00:26:37 +02:00
Codex ed4af44226 string: skip UTF-8 decoding for ASCII wrapping
Advance directly over ASCII bytes in both wrapping paths. Keep multibyte decoding unchanged and verify that ASCII wrapping produces the expected text without calling utf8skip.
2026-10-12 00:25:31 +02:00
Codex fe04a9a7bc string: avoid replaying wide glyphs at line breaks
Emit a break before the current wide glyph and carry its measured width forward when the glyph fits on the next line. Keep the existing fallback for earlier break positions and constrained buffers. Add a regression check that these breaks scan each glyph at most once.
2026-10-12 00:22:45 +02:00
Codex 0a6d081ea2 string: avoid rescanning wrapped word tails
Keep the glyph count after the chosen space instead of rewinding and decoding that span again. Retain the existing embedded-newline fallback and early-return decisions so wrapping output and line limits stay unchanged.

Count glyph scans in the regression harness to verify ordinary word-boundary wrapping visits each glyph at most once.
2026-10-12 00:15:18 +02:00
Codex dea10ccf08 Limit core latency changes to the affected audio drivers
Reset playback audio only when its effective latency target changes, including the configured floor. Handle enabled microphones independently because their latency also includes the core request. Preserve startup and staged-load deferral.

Keep recording, video, input and unrelated devices alive. A latency request changes no recording parameters and needs no cached-frame replay, aspect update or cursor change. Use the audio-specific reset to avoid repeated menu-audio initialization.

Extend the setting-command regression to cover limits, clearing, floors, microphone-only changes and deferred requests.
2026-10-12 00:13:53 +02:00
Codex cae9ec0154 string: wrap text with one input copy
Copy the input once and only replace the spaces chosen as line breaks. Advance over UTF-8 spans without writing them again when the wrapping loop rewinds. Preserve wrapping decisions, line limits, return lengths and short-buffer rejection.

Add regression cases for word boundaries, multibyte text, existing newlines, leading spaces and bounded destinations.
2026-10-12 00:07:20 +02:00
libretroadmin 2e1a28a663 D3D12: frames wait for their predecessor's signal, not a queue drain
The frame paths (the frame itself, black frame insertion and present
repeat) begin with a wait for the last frame command list's own signal
instead of a drain of the whole queue, so the work a hardware core has
queued since is no longer waited for, and the frame list is signalled
as it is submitted. Overlay pages and their sprite buffer go behind
the fence of the frame that may still read them instead of a drain; a
rotation change and the menu texture wait only for that frame. The
lent software framebuffers are three, and a lend takes one no copy in
flight reads, or is declined: it never waits.

tools/d3d12_idle_wait_check.py, run in CI beside the Vulkan check,
fails on any queue drain or fence wait in the driver that is not on
tools/d3d12_idle_wait_allow.list, which names the lifecycle ones left.
2026-10-12 00:04:58 +02:00
Codex f2650811e3 gfx: cache vertical ticker wrapping and line views
Reuse bounded, owned line layouts across frames, keyed by text, wrapping width and mode, font identity, generation and scale. Cached line views avoid rewrapping, reparsing and copying the full intermediate block; stepped windows use direct line offsets.

Retain scratch-buffer fallback for oversized layouts and allocation failures, and flush cached storage at animation deinit. Cover warm-frame wrapping and allocation counts, invalidation, eviction, reused source buffers and fallback behavior.
2026-10-11 23:59:04 +02:00
libretroadmin 16cbbdc00c gfx_surface_player: thumbnails, companions and overlays on video_player
gfx_surface_player is video_player with a gfx_surface as its sink:
frames are decoded into the surface's slots and submitted from there,
a frame the driver dropped is sent again when the slot comes back,
and the surface stays the consumer's. Animated thumbnails play through
it as before, with their window feed and preview audio left where
they were; the desktop companions' preview thread drives a player in
line and scales from its canvas; looping overlay images play through
one each, still looping whatever the file's count says.

The companions and overlays gain the shared pacing: durations kept
to the file's rate instead of reset per frame, and catch-up when the
decode falls behind. overlay_apng now plays the fixture the way the
overlay does, and a new on-screen lane runs an animated overlay in
the real binary, threaded and not.
2026-10-11 23:46:04 +02:00
libretroadmin f89bddf818 video_player: animation playback for libretro-common
Plays an image_transfer animation stream (WEBM, MP4, WEBP, APNG) to a
sink on a clock the caller supplies: each frame on its container
duration, the loop count, and catch-up that has the stream pass over
unreferenced pictures while the decode is behind. The decode runs on
a shared worker with two jobs, so one frame is decoded while the one
before it waits to be shown, or on the polling thread for a sink that
asks for it, or a build without threads. The sink owns where frames
go: the slots they are decoded into, and showing them.

This is the engine that gfx_thumbnail carried privately, lifted out
unchanged in behaviour and lock-free as before, so the menu, the
desktop companions, overlays and cores can share it.
video_player_test drives it against a scripted stream, threaded under
TSan and threadless.
2026-10-11 23:46:00 +02:00
danprice142andCodex 0cd2e262eb gfx: share vertical line tickers with menu drivers
Extract the line-ticker refactor from PR #19384, commit 6c688c5a01. Move the stepped and smooth vertical tickers and their timing constants from XMB to gfx_animation.

Keep current master behavior, including caller-owned wrapping scratch storage, packed field dimensions, cached font metrics and UTF-8 wrapping. Add regression coverage for scrolling, fades, bounded output and scratch reuse.

Co-authored-by: Codex <[email protected]>
2026-10-11 23:42:13 +02:00
Codex 8bf98af186 Resize windowed output without restarting unrelated drivers
Advertise live resizing separately from fullscreen transitions for Windows WGL, Vulkan and D3D10/11/12. Share it between menu and queued scale requests, with video/input-only recovery on unsupported or failed resizing.

Defer resizing during fullscreen, including CLI-forced fullscreen. Avoid exclusive Vulkan renegotiation when fullscreen state does not change. Cover menu, queued, deferred and failed resize requests in the setting-command regression.
2026-10-11 23:41:45 +02:00
Codex dc14608c24 Preserve unrelated state during fullscreen recovery
Apply menu fullscreen selections through the hotkey transition path and limit failed transitions to video/input reconstruction. Preserve suspended audio, core metadata and unrelated display resources across scoped resets, and avoid reopening menu audio for video-only resets.

Cover menu apply semantics, CLI fullscreen overrides, failed and unsupported transitions, audio retention and scoped metadata lifetime in the setting-command regression.
2026-10-11 23:20:10 +02:00
libretroadmin 92ba55c2c3 Threaded video: frame buffers in driver memory, uploaded where they lie
The threaded wrapper's ring slots and spare are now taken from frame
memory the driver hands out (frame_mem_alloc/ready/free, new poke
hooks). A frame the push copied in, or the core rendered into a loan,
goes to the GPU from where it lies: the video thread no longer copies
it into the driver's staging texture. Vulkan implements the hooks with
persistently mapped host buffers (cached where the device has them)
and copies out of them on the GPU, with the compute conversion for
RGB565 where the format cannot be sampled.

A buffer a submitted frame still reads is marked busy by the video
thread and released once the driver's fence for that frame has
signalled, polled, never waited on. The main thread writes only a free
buffer: a busy slot or spare is swapped for another from the pool, or
for a host buffer when every driver one is in flight. Drivers without
the hooks keep the host buffers and behave as before.

The threaded-video harness gains a frame memory lane: copied frames,
lent frames and a cropped window, each drawn from frame memory with no
CPU copy on the video thread, and identical on screen to the same
frame drawn with threaded video off.

GFX_INSTRUMENT counts frames drawn this way (frame_mem), and the
upload_submit counter gets the name its report line was missing.
2026-10-11 23:16:30 +02:00
Rob LoachandCodex 1682b6bcd4 MCP: tools take named parameters, not one string (#19792)
* mcp: tools take named parameters, not one string

* mcp: preserve explicitly supplied empty parameters

Apply defaults only to absent parameters and retain separators after empty operands. This lets SET_SHADER accept an empty preset to disable shaders.

* mcp: normalize named integer parameters before dispatch

Convert integral JSON decimal and exponent forms to plain decimal digits without floating-point rounding. Reject fractional values, numeric strings and expansions beyond the request limit before running handlers. Keep the legacy opaque argument path unchanged.

---------

Co-authored-by: Codex <[email protected]>
2026-10-11 23:09:28 +02:00
Codex 12037dedc0 GL/DXGI: retain framebuffer objects across resizes
Detach old shared textures before releasing image imports, then attach resized textures to the existing framebuffer objects. Release framebuffers only on full presenter teardown and preserve caller bindings during resize.
2026-10-11 22:57:51 +02:00
Codex 46c3666112 Vulkan/DXGI: retain frontend sync across compatible resizes
Preserve frame fences, presentation semaphores and acquire tracking when resizing an existing DXGI presenter. Allocate missing presentation semaphores only, and retain full cleanup when presenter resizing fails or presentation falls back.
2026-10-11 22:46:31 +02:00
Codex 418d5c7449 Vulkan/DXGI: fence resize work instead of draining queue
Submit a reusable fence after preceding queue work and wait outside the queue lock before resetting presenter commands and replacing images. Keep the full drain on teardown and the existing resize failure fallback.
2026-10-11 22:31:33 +02:00
Codex 0ca255df8d DXGI: reuse command objects across presenter resizes
Keep D3D12 command allocators and lists, and Vulkan command buffers, alive across compatible presenter resizes. Reset and record them after the existing GPU idle waits, retaining the resize failure fallback and releasing objects on full teardown.
2026-10-11 22:21:05 +02:00
libretroadmin 22dd539087 samples: ratrac3p test, and run it in CI
Checks ratrac3p against a reference decoder's output for a stereo
stream (tones, gain points in both channels, explicit and all-band
swap and negate masks) and a mono one, then the s16 output, the fmt
chunk, bad frames leaving the history alone, reset, and garbage
under the sanitizers.
2026-10-11 22:15:02 +02:00
libretroadmin fec375bd84 ratrac3p: an ATRAC3plus decoder, clean-room
Decodes the ATRAC3plus streams of PSP games and OpenMG files (mono
and stereo channel blocks, up to 7.1), to float or s16, 2048 samples
a channel a frame.

Written without reading another decoder's source: the bit-stream
syntax and every table were established by feeding a reference
decoder crafted and rewritten streams through its public interface
and reading off its return codes and output. Against that decoder it
matches to float rounding on every frame of the game streams and
encoder output it was checked on.

SSE2 and NEON paths with a plain C fallback (RATRAC3P_NO_SIMD); about
three times as fast as PPSSPP's FFmpeg decoder on the same streams,
the plain C path over twice. C89, no stack frame over 2 KiB, the
per-channel state sized to the stream.
2026-10-11 22:14:58 +02:00
Codex 0085f38176 cocoa: adopt window restoration protocol at runtime 2026-10-11 22:13:19 +02:00
Codex 1a7304f4b3 tests: declare window restoration in AppKit stubs
Keep the Metal compile-matrix stand-ins aligned with the Cocoa window restoration protocol adoption.
2026-10-11 22:10:28 +02:00
Codex 49a4a7da2d DXGI: retain presentation state across window resizes
Resize the existing swapchain and replace its size-dependent images instead of recreating the D3D12 device, queue, factory and shared fences. Retain the GL and Vulkan fence imports and Vulkan command pool. Compatible dimensions and format require no resource replacement.

Drain both APIs before replacing their resources, retaining teardown safety and the existing recreation fallback. Submit the first resized frame before waiting for frame latency, and preserve the Vulkan image index when acquisition cannot provide a frame. Cover retention, pacing, draining and acquire failures with portable regressions.
2026-10-11 22:01:00 +02:00
Codex 2603eda4f4 cocoa: declare window restoration protocol conformance 2026-10-11 21:34:35 +02:00
Codex f55d947c55 DXGI: drain presentation work before destroying the bridge
The shared copy fence is signalled before Present, so waiting for its last frame value does not protect swapchain resources from pending presentation work. This trips D3D12 OBJECT_DELETED_WHILE_STILL_IN_USE during video reinitialization and exit. Signal and wait for a new queue fence value before releasing resources, including submissions that failed before advancing the frame sequence. Add portable teardown regression coverage.
2026-10-11 21:22:01 +02:00
Codex feb6d23b37 video: add optional Windows DXGI presentation for GL and Vulkan
Keep native presentation as the default and fall back when D3D12 or graphics interop is unavailable. Match the rendering adapter and share GPU resources and fences with a frame-latency-one flip swapchain, retaining the presenter across occlusion and compatible window transitions.

Load new Windows APIs dynamically and support Vulkan 1.0 external-fence interop. Add portable regression coverage for pacing, queue ordering, occlusion, tearing and failure bookkeeping.
2026-10-11 21:00:38 +02:00
libretroadminandClaude Opus 5.5 eff61075d4 samples/audio/live_drivers: Pulse's ring is checked for its mirror
A lent span shorter than asked is no sign of a wrap - the room the
server granted bounds it too - so counting those failed under TSan and
was dropped, and with it any check that the ring is mirrored. The test
now includes the driver, as it does ALSA's, and asks the ring itself.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BbMMaa7pA2TMyXf47yP5jB
2026-10-11 20:16:44 +02:00
Codex addf0ca242 Cocoa: check window restoration availability at runtime
Use runtime selectors to register the restoration handler without depending on the 10.7 SDK declarations or protocol. Keep only the blocks compiler guard so older GCC builds remain supported and blocks-capable older-SDK builds can restore windows on newer systems.
2026-10-11 20:15:04 +02:00
Codex c2a4f3b79f Cocoa: restore the programmatic main window at launch
Register the main window with AppKit restoration and return the existing window when restoring its saved state. Defer showing the window until restoration and keep initial video setup from replacing the restored frame or fullscreen transition.

Gate restoration on blocks-capable builds with a 10.7 or newer SDK, and check API availability at runtime so Tiger and Leopard builds retain their existing path.

Fixes #18991.
2026-10-11 20:10:31 +02:00
Codex c8bd052dc3 Android: queue cached pulses for keypress haptics
Send keypress feedback as a short default-amplitude pulse instead of relying on view feedback or a predefined click. Cache the immutable effect, touch attributes and vibrator capability. Reuse one UI callback and coalesce pending posts, keeping the native caller free of vibrator service work.

Read the system touch-feedback setting at dispatch time, reject unfocused feedback, and remove queued work on activity destruction. Keep guarded legacy API paths and other view haptic effects.

Candidate follow-up for #19791; Galaxy A07 physical validation remains required.
2026-10-11 19:53:22 +02:00
Codex 1a51e325db WGL: toggle exclusive fullscreen without driver restart
Use the Win32 display-mode transition on the existing window and retain the GL context. Propagate failed or missing mode callbacks through all GL drivers so the frontend can fall back to reinitialization.
2026-10-11 19:52:20 +02:00
Codex 2817570339 Fix stale mock readiness in Mutter displayconfig tests
Clear the mock log before launching the background process so polling cannot consume the previous instance readiness marker and start a test before the new mock owns its D-Bus name.
2026-10-11 19:47:50 +02:00
libretroadminandClaude Opus 5.5 6282f75bc0 audio: the shallow pipe primes to its setpoint and a publish
Primed to the setpoint alone, a display-paced pipe started the device
on a single publish: at 16 and 32 ms Audio Latency it held next to
nothing ahead of each of the next publishes for a second and more,
while rate control climbed to the setpoint at its half percent, and the
first second of play now and then carried a gap. It now primes to where
rate control holds it just after a publish - the setpoint and the
publish - so the device starts where it is to stay.

pipeline_clocked checks the first second against that, and its display
schedule no longer catches up on frames a host stall made it miss: a
display shows the next vblank, and catching up held the core at the
audio's pace for seconds, a backlog no runloop builds.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BbMMaa7pA2TMyXf47yP5jB
2026-10-11 19:42:40 +02:00
Codex 254d5f07c8 D3D10: toggle exclusive fullscreen in place
Keep the device and drivers alive through the existing Win32 display-mode transition. Report failed mode changes through the driver reinitialization fallback.
2026-10-11 19:40:59 +02:00
Codex a26be890fc D3D11/D3D12: retain compatible HDR resize textures
Keep HDR intermediate render targets across swapchain resizes when their size and requested format match. Recreate missing or incompatible resources while preserving color-space and metadata updates.
2026-10-11 19:22:12 +02:00
Codex f6959e9545 D3D11/D3D12: toggle exclusive fullscreen in place
Reuse the Win32 display-mode transition and keep the device and drivers alive. Report failed transitions through the existing driver reinitialization fallback.
2026-10-11 19:22:09 +02:00
Codex 844f03ba7d d3d8: tolerate repeated cleanup during device recovery
Restore releases the menu vertex buffer before resetting the device. If reset fails, initialization cleans up again with that buffer already cleared. Guard the release so recovery and partial initialization do not dereference a null COM object.

Add a source-based cleanup regression covering missing resources, repeated teardown and replacement buffers on Linux and Windows.
2026-10-11 19:20:06 +02:00
Codex b02e776b9c Windows Vulkan: toggle exclusive fullscreen without driver restart
Preserve the window, Vulkan device and other drivers during exclusive fullscreen transitions. Restore the desktop on exit and recreate the swapchain with exclusive access disabled for windowed mode. Report context failures to the frontend restart path, and retain that path for mailbox acquisition threads.
2026-10-11 19:12:33 +02:00
Codex 877b56a128 d3d12: allow compute sampling of generated mip levels
The static compute sampler defaults MaxLOD to zero, clamping every mip-generation read to the base level. Allow the full chain so each dispatch reads the preceding mip instead of aliasing the original texture.

Exercise the shipping sampler initialization in the portable mipmap test and reject source levels outside its LOD range.
2026-10-11 18:57:15 +02:00
Codex 65c6a192b2 android: normalize Center in keyboard callbacks
HDMI-CEC and keyboard-classified IR remotes send DPAD_CENTER for OK. Normalize it to ENTER for both callback key translation and character lookup, sharing the rule used by the held keyboard state. Raw joypad keycode 23 and source routing remain unchanged.

Extend the C89 keyboard-state and CEC routing fixtures to cover normalization and press/release parity.

Fixes #19249
2026-10-11 18:56:59 +02:00
Codex 1c54ecd3e1 Wayland Vulkan: negotiate live fullscreen changes asynchronously
Keep mapped Vulkan windows rendering while the compositor configures fullscreen changes. Apply geometry only after configuration, acknowledge before surface commits, and retain blocking negotiation for initial setup and reinitialization.
2026-10-11 18:48:36 +02:00
Codex c785e8c35c d3d12: generate requested shader render-target mipmaps
Allocate full mip chains only when the next shader pass requests them, and share generation between uploads and render targets. Use compute-readable states during generation and restore pixel sampling afterward.

Size descriptors for the full chain without oversized shifts. Initialize feedback mips, avoid division by zero for transparent texels, and release descriptors before replacing their mip count.

Add portable generation tests and extend feedback allocation coverage. Addresses the D3D12 render-target portion of #16567.
2026-10-11 18:45:35 +02:00
Codex b30626f34e slang: select mip filtering from the pass input
Use the pass's mipmap request and resolved input filter when choosing mip-level interpolation in GLCore and Vulkan, for preset loading and pass recompilation. Output mip allocation belongs to the next pass and must not control the current input sampler.

Addresses the GLCore/Vulkan portion of #16567.
2026-10-11 18:31:47 +02:00
Codex 523c5b8937 audio: keep in-place fullscreen toggles active
Suspend audio only when a fullscreen toggle falls back to driver
reinitialization. Successful in-place mode changes preserve audio state
instead of dropping callbacks while waiting for the compositor.
2026-10-11 18:17:04 +02:00
Codex 7568169787 Vulkan: retain resources across swapchain image-count changes
Keep compatible renderer frame resources up to their allocated capacity,
adding slots only when the swapchain grows beyond it. Grow shader sync
storage as needed while retaining layouts, pipelines, history and feedback.

Release retained capacity on incompatible format/HDR changes or teardown.
2026-10-11 18:04:17 +02:00
Codex 3ac110198d Vulkan: preserve frame synchronization when reusing the swapchain
Decide whether the swapchain and acquire path can stay in place before
draining submissions and clearing frame fences. A no-op resize or an
interval change that resolves to the same present mode now preserves
the fences and frame index.

Keep the existing drain and fence setup for swapchain replacement and
mailbox-emulation transitions.
2026-10-11 17:42:13 +02:00
libretroadminandClaude Opus 5.5 bd24443dc4 audio: Shallow Pipeline Queue on by default
The threaded pipeline is the default where threads are, and without the
fill cap it holds far more than the frame-synchronous path it replaced:
an audio-paced core sits behind a full ring, 182 ms queued at 16 ms
Audio Latency against 17 ms with the cap, and a display-paced one 91 ms
at 64 ms against 44 ms. With the cap it holds what the inline path
holds, and meets late frames as the inline path does. The setting stays
as the way back to the deep queue.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BbMMaa7pA2TMyXf47yP5jB
2026-10-11 17:29:53 +02:00
libretroadminandClaude Opus 5.5 9ff8ad9d3c audio: rate control stands aside for a blocking write that is the only clock
With audio the only thing holding the core - Audio Sync on, vsync off,
no Scanline Sync, display pacing or frame limiter - the write waits for
room, so the device sits full, and rate control read that as overfill
and corrected at its whole delta: the core ran up to half a percent
fast and the audio as much sharp, inline as on the threaded pipeline.
There is one clock and nothing to correct. Where the last iteration's
pace was audio alone and the write is seen waiting - inline, less room
than the batch needs; threaded, the publish held - the overfill side is
not corrected. A present that blocks whatever vsync says keeps the fill
at the setpoint, short of any wait, and is corrected as ever.

Audio-paced, rate control's mean adjustment: inline 0.9962 -> 1.0000,
threaded 0.9950 -> 1.0000. pipeline_clocked gains the lane, including
a display 0.2% either side of the content behind the runloop's back;
rate_control_bounds pins each case.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BbMMaa7pA2TMyXf47yP5jB
2026-10-11 17:29:48 +02:00
Codex 4c844a3e9c video: move fullscreen exclusivity policy into the video driver
Expose video_driver_fullscreen_is_exclusive() and use it for the
fullscreen toggle. Keep the display-specific distinction between
Wayland fullscreen and exclusive presentation in video_driver.c.

Preserve the existing toggle routing and pointer capture policy.
2026-10-11 16:37:01 +02:00
Codex d03da0c72f input: Avoid rereading shared hotkey sources for menu chords
The hotkey is already known to be pressed. Check only independently bound sources when deciding whether it cancels the menu chord, and stop checking once that chord is cancelled.

Add driver-call assertions for shared bindings, unrelated hotkeys, and mixed keyboard/controller bindings.
2026-10-11 16:36:39 +02:00
Codex 27c0878904 Wayland: keep fullscreen toggles in place regardless of windowed setting
Wayland fullscreen is compositor-managed, so disabling Windowed
Fullscreen does not request an exclusive display mode. Let contexts
that support in-place toggling keep the renderer alive in either case.

Use the same distinction for the toggle's pointer capture policy. Keep
the reinit fallback for contexts without in-place support and for actual
exclusive fullscreen on other display types.
2026-10-11 16:32:50 +02:00
Codex 819c795357 Wayland/Vulkan: toggle fullscreen without rebuilding the renderer
Advertise fullscreen-in-place support and reuse the existing Vulkan
surface when changing the window state. Let the normal resize path
update the swapchain after the compositor's configure is processed.

This keeps the device, shader chains and HDR resources alive instead of
reinitializing drivers for a borderless fullscreen toggle. Retain the
existing configure handshake and initial surface creation path.
2026-10-11 16:18:33 +02:00
Codex f767d6ed51 input: Allow menu chords with shared Hotkey Enable bindings
Do not cancel a pending menu toggle for a hotkey activated only by the same physical input as Hotkey Enable. Resolve controller profile fallbacks while retaining cancellation for independently held inputs and overlay or remote presses.

Add portable coverage of the production cancellation and release code, and controller chord checks in the frontend harness.

Fixes #19087.
2026-10-11 16:18:03 +02:00
Codex 5ed07ff90e Wayland/Vulkan: declare the window surface opaque
Mark the Vulkan window surface opaque before creating the WSI surface. This supplies the compositor hint when opacity is inherited from the application, without relying on the driver to provide it.

Use the same full-surface region as the EGL HDR path so resizing and display scaling do not leave uncovered areas. Direct scanout still depends on compositor and buffer compatibility.
2026-10-11 16:05:23 +02:00
Codex cdaf697c74 D3D11/12: initialize feedback before the first swap
Clear both textures in each feedback pair when creating or resizing it, so the first frame reads defined black history. Transition D3D12 feedback resources to PIXEL_SHADER_RESOURCE alongside the outputs before swapping; otherwise the first draw samples one texture and renders to the other with incorrect resource states.

Run the production allocation, preparation and swap code in a portable resource-model harness. Cover final-pass self-feedback, earlier passes reading later-pass feedback, intermediate feedback, viewport offsets, repeated frames and resize. Add Linux sanitizer and MinGW/Wine CI runs.

Refs https://github.com/libretro/RetroArch/issues/15844
2026-10-11 15:45:57 +02:00
Codex e6b8711cf5 D3D12: allocate final-pass feedback at viewport size
A pass that supplies feedback needs its own render target even when its output matches the viewport. Include feedback in the allocation condition, as D3D11 already does, and render at the offscreen texture origin instead of the window viewport offset.

This gives self-feedback and earlier passes reading the final pass valid, unclipped textures.

Refs https://github.com/libretro/RetroArch/issues/15844
2026-10-11 15:45:53 +02:00
libretroadminandClaude Opus 5.5 0e7286c735 audio: the pipeline fill cap holds an audio-paced core where inline would
Paced by audio alone, the core was held once the pipe held the target
and a publish: two publishes queued past a full device. An inline write
blocks until its frame is in the device, the device's buffer being the
cushion; the pipe needs only what the consumer moves in a pass, a chunk
of that buffer. With audio_pipeline_fill_cap and no display pacing, the
cap is that chunk. A display-paced core keeps its two publishes over
the target.

Audio-paced, queued ahead of each publish: 16 ms 45 -> 17, 32 ms
57 -> 32, 64 ms 78 -> 66, against 14, 28 and 56 inline; a core taking
12 ms a frame at 16 ms runs as clean as inline does. pipeline_clocked
now fails a cap that leaves more than a publish past the device.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BbMMaa7pA2TMyXf47yP5jB
2026-10-11 15:44:58 +02:00
libretroadminandClaude Opus 5.5 b28b3dbddb samples/audio/opensl: no capture of a block the driver has freed
The pump took a block off the queue, slept its playing time, then
copied it into the capture - with nothing stopping the driver from
clearing the queue, destroying the player and freeing its buffers
during the sleep. ASan caught the copy reading freed memory. A clear
and the player's destroy now bump a generation under the queue lock,
and the capture is taken under it only when the block is still the
device's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BbMMaa7pA2TMyXf47yP5jB
2026-10-11 15:44:54 +02:00
Codex 8619999dfe SMB: preserve EEXIST when creating an existing directory
Map SMB2 name collisions to -EEXIST after a valid CREATE response and propagate the result through rsmb_mkdir. This lets cloud sync reuse existing directories without hiding transport failures.

Add coverage for directory creation, an existing directory, and a missing parent to the SMB integration harness.

Fixes https://github.com/libretro/RetroArch/issues/19778
2026-10-11 14:20:16 +02:00
Codex a4afa5a6b8 Android: add a touch haptic vibrator fallback
Use virtual-key feedback for overlay presses and respect system touch settings. If view feedback is unavailable, request a click through the device vibrator with touch attributes, using a short pulse on older APIs.

The fallback follows the effect selection in PadKit 1.0.0-beta1, used by Lemuroid. This is a candidate fix for #19791; Galaxy A07 verification is still required.
2026-10-11 14:17:57 +02:00
Clownacy 688b779a1a Restore RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS backwards-compatibility
In libretro#19161 and libretro#19169, the clash reported in
libretro/libretro-common#89 was addressed by changing
`RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS` from 44 to 87.

This is not ideal, as it breaks backwards-compatibility with existing
frontends, including stable releases of RetroArch. Additionally,
while the documentation does explicitly say that commands tagged with
`RETRO_ENVIRONMENT_EXPERIMENTAL` may be subject to change in the
future, `RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS` is not one of
those commands, making this breakage a violation of the promises made
by the API.

To resolve this, `RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS` has
been moved back to 44, with a compatibility shim left to occupy 87,
and `RETRO_ENVIRONMENT_EXPERIMENTAL` has effectively been deprecated,
now just being a regular part of the command ID. This allows the two
clashing commands to be distinguished by their experimental bit.

The documentation does say that `RETRO_ENVIRONMENT_EXPERIMENTAL`
should be filtered out by frontends, but not even RetroArch actually
does this. In my own libretro frontend, I had to resort to this same
hack to work around the clashing commands.

Since experimental commands never seem to change or be promoted to a
non-experimental command ID, I think it is best to retire the bit
entirely and just let it be part of the ID. For good measure, the
constant could be removed, and the bit baked directly into the
command IDs, that way frontend developers who do use the bit are
implicitly notified to stop filtering it out, and be nudged towards
the 'correct' behaviour
2026-10-11 14:04:24 +02:00
libretroadmin 840d75e63f REsync 2026-10-11 13:47:23 +02:00
libretroadminandClaude Opus 5.5 2b40b83bc0 audio: the pipeline fill cap holds the frame-synchronous path's queue
Rate control on the threaded pipeline steers the device and the pipe
together to about seven eighths of the buffer and a publish on top,
where the frame-synchronous path steers the device to half the buffer:
display-paced with Audio Sync, the default, 91 ms queued at 64 ms
Audio Latency against 47 ms inline. With audio_pipeline_fill_cap the
controller takes the inline setpoint - half the buffer before the
publish, never under a publish - and priming fills to it rather than
to the device's buffer and the target, which the controller's half
percent would take seconds to bleed off. Off, nothing changes.

Display-paced, queued ahead of each publish: 16 ms 44 -> 29, 32 ms
58 -> 28, 64 ms 91 -> 45, with a core up to 16 ms late a frame as
clean as the inline path. The Threaded Pipeline's description no
longer claims the inline path's latency without it. pipeline_clocked
gains the lane, against the inline setpoint.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BbMMaa7pA2TMyXf47yP5jB
2026-10-11 13:37:34 +02:00
libretroadmin ab2014af62 Resync libretro-common 2026-10-11 13:36:51 +02:00
libretroadmin 9a0da6a98b Resync libretro-common 2026-10-11 13:27:11 +02:00
libretroadmin 8cdeb761e2 rh264: B pictures ahead of PPSSPP's FFmpeg
A P or B picture's first slice decodes straight into the picture's
motion grid, so single-slice pictures no longer fold a working copy
across, row by row; later slices keep the working grid. An I picture's
rows go into the picture grid as intra with their filtering, so a
picture decoding concurrently never reads a stale co-located cell.
Weighting with the identity weights (2^denom, no offset), the usual
case for a reference without weights of its own, is skipped, and
explicit or implicit bi-prediction with equal unit weights takes the
plain average. Motion compensation at the picture edges gathers its
window a row at a time instead of clamping every sample, direct cells
are built once and copied, and the skip paths clear their flags a row
at a time.

Output is identical on every frame of the PSP streams and the x264
test set, weighted fades included, on one thread and on a pool. On a
480x272 PSP stream with B pictures rh264 decodes 1.07-1.09x as fast as
PPSSPP's FFmpeg (was 0.97x), and 1.41x without (was 1.20x).
2026-10-11 13:01:45 +02:00
Anthony SchemelandClaude Opus 5.5 4a139de0b8 linked_list: remove_*_matching do nothing on a NULL matches (#19806)
linked_list_remove_first_matching, _last_matching and _all_matching
checked the list but not the matches callback, so a NULL callback on a
non-empty list was called through. get_first_matching and
get_last_matching already return on it. The three now do the same,
and the header says so.

Tests: remove_{first,last,all}_matching_function_null, on a list of
two items. Before: an ASan SEGV in each. After: each returns without
touching the list.

write-test: red on upstream master (3 ASan SEGVs, 89 of 92 pass), green after (92 of 92).

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-11 12:58:05 +02:00
Acts1631 6d0548e98a imageviewer: validate stb JPEG Huffman tables (#19800)
A crafted DHT segment can describe more symbols than fit in the fixed
Huffman arrays, causing out-of-bounds writes while building and copying
the table. Malformed tables can also produce an out-of-range symbol
index during entropy decoding.

Reject oversized size lists and DHT symbol counts, and validate decoded
symbol indexes before accessing the table. This backports the relevant
checks from upstream stb_image.
2026-10-11 12:54:59 +02:00
Acts1631andCodex 2e4c6390cc imageviewer: validate stb image dimensions (#19799)
* imageviewer: validate stb image dimensions

The bundled stb_image decoder calculates allocation sizes and pixel
offsets with 32-bit integers. Crafted dimensions can wrap an output
allocation before the decoder writes the declared pixels.

Read only complete files that fit the decoder's int length API. Query
the dimensions first and reject images whose RGBA output would exceed
INT_MAX, preserving valid top-down BMP heights.

* imageviewer: verify PIC signature during preflight

Check the PIC signature before interpreting its metadata. Without this
check, stbi_info_from_memory can mistake TGA pixel bytes for PIC dimensions
and the imageviewer preflight rejects a valid image that decoded before.

Reuse the existing signature probe, which rewinds the stream, so other
formats continue through their own metadata readers.

---------

Co-authored-by: Codex <[email protected]>
2026-10-11 12:43:35 +02:00
Acts1631andCodex d5098c0ab3 imageviewer: bound PSD and HDR RLE packets (#19798)
* imageviewer: bound stb RLE runs

The bundled stb decoder accepts PSD and HDR RLE packets that extend
past the remaining output. The resulting writes can overrun the heap
image or scanline buffer.

Reject oversized packets before decoding them. Also reject zero-length
HDR packets so malformed input cannot stall the decoder indefinitely.

* imageviewer: reject overflowing PSD dimensions

Check PSD dimensions before multiplying the output allocation size and
check the compressed row-table size before skipping it. A large width
could wrap the allocation to four bytes while the decoder still accepted
an RLE run within its logical pixel count, writing past the allocation.

Reject nonpositive dimensions as well. Keep valid image decoding unchanged.

---------

Co-authored-by: Codex <[email protected]>
2026-10-11 12:31:36 +02:00
libretroadmin 042259e136 rh264: faster CABAC residuals and P/B reconstruction
The arithmetic decoder took the bin decision as a select so its outcome
costs no misprediction, then branched on range < 256 to renormalise -
a branch taken on every LPS and on some MPSs, so it mispredicted about
as often as the bin itself.  Renormalise every time instead, the shift
(0 once range is back at 256 or more) from a table indexed by range.
codIOffset now sits at a fixed bit of the offset word with the
lookahead below it: the comparison against range shifts the offset
down off the critical path, and renormalisation shifts the word up.
rangeTabLPS is laid out to be indexed by (range & 0xC0) * 2 + state,
a mask and an add.  The refill takes four bytes at a time away from
the end of the slice.

The residual loop runs the engine on locals, coded_block_flag
included: through the struct, every context-state byte store (a char
store, free to alias anything) sent range, offset and bit count back
to memory and in again.  For P and B macroblocks it also writes the
4x4 luma and chroma AC levels in raster order and dequantised - one
multiply for each coefficient coded, (level * qm + 32) >> 6, qm
folding the scale and the qP shift, exactly the separate pass's
result - in place of the scan scatter and a dequantisation pass over
all sixteen.  Chroma blocks with no AC skip the transform: nothing to
add when the DC is zero too, else the DC spread over the block.

Deblocking P and B pictures built the 16-bit coefficient mask with a
test per flag and asked eight times about the neighbours' blocks; now
one SSE2 compare builds it, the neighbours' bits come from their
masks, an edge's tC0 come from one five-entry row by bS, and the
uniform-motion check compares cells as two words.  The macroblock
position steps instead of dividing.

The working motion grid is reset to 'not decoded' at every slice and
folded into the picture's grid: the reset stored the 24-byte cells
field by field, and a single-slice picture was folded twice, row by
row with its filtering and then whole at the slice's end.  Now the
reset block-copies a prepared run of cells, whole rows fold as one
copy, and the slice-end fold starts after the rows already folded.

Output is identical on every frame of the PSP streams and of x264
streams with multiple and partial-row slices, temporal and spatial
direct, weighted prediction, 8x8 transforms, 4:2:2, 4:4:4, 10-bit,
lossless, CAVLC and MBAFF, on one thread and on a pool, natively, on
Windows (MinGW, under Wine), big-endian PowerPC and AArch64 NEON.
Against PPSSPP's FFmpeg on a 480x272 PSP stream, decode speed: with B
pictures 0.75x to 0.97x, without 0.87x to 1.20x.
2026-10-11 12:11:15 +02:00
Codex 4a71515435 android: wait for a window after startup permissions
Opening All Files Access Settings can retire the initial native window. The permission result may arrive before Android delivers its replacement, letting startup continue without a window and fail video initialization.

Keep pumping lifecycle events until permission is resolved and a window is available.
2026-10-11 11:53:24 +02:00
BigBossWHDandCodex 7c39ecba57 Cocoa: defer native termination until core teardown (#19802)
* Cocoa: defer native termination until core teardown

* Cocoa: preserve pending system termination requests

Return NSTerminateLater while the run loop saves and unloads the core, then acknowledge that request from Cocoa deinit. Canceling termination aborts logout, restart, and shutdown even if the app quits afterward.

Suppress duplicate quit events and start the existing common-mode draw observer for Qt builds so teardown can progress in AppKit's modal termination loop. The termination APIs are available in the OS X 10.4 SDK.

---------

Co-authored-by: Codex <[email protected]>
2026-10-11 11:27:59 +02:00
Codex 4bde1eb779 aac: shrink Huffman builder stack scratch
Store subtable bit widths in uint8_t, matching the input bit lengths. This reduces the 512-entry scratch buffer from 2048 to 512 bytes and brings raac_huff_build below the 2048-byte stack budget.
2026-10-11 11:07:53 +02:00
Craig 4f1610462e quest: add var if meta build so can show core downloader, fixUp xml (#19805) 2026-10-11 10:57:19 +02:00
libretroadminandClaude Opus 5.5 f5fba98599 ratrac3: the plain C path ahead of PPSSPP's decoder too
Targets without SSE2 or NEON ran the four-lane code on a struct of four
floats, which made the QMF tree - most of the work - reload every tap
four times; built that way the decoder only matched PPSSPP's. The QMF
now has a plain C loop of its own, four output pairs a pass in eight
scalar accumulators, and the s16 conversion rounds without a branch on
the sign. Built without SIMD it decodes 1.35 times as fast as PPSSPP's
decoder, and with SSE2 three times as fast, as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HSDanAkwZ5QRTtfLqkEh68
2026-10-11 10:14:15 +02:00
Codex 90e727984d Fix platform builds and sample CI checks
Provide GL_LUMINANCE for the planar converter when core GL headers omit it. Keep Tiger builds from opting into Leopard's UNIX2003 symbol aliases.

Keep PSMF and ATRAC3 sample objects inside their own directories. Allow the audited ATRAC3 joint-stereo state copy between distinct array rows.

Accept partial PulseAudio lends: wait_writable can return less than requested because the server's minimum request bounds the wait. A short lend alone does not imply a ring wrap.
2026-10-11 10:13:26 +02:00
libretroadminandClaude Opus 5.5 362d1c9450 rh264: deblocking skips the inside of uniform inter macroblocks
An inter macroblock with no luma coefficients whose sixteen cells carry
one motion has boundary strength 0 on every internal edge, in luma and
chroma alike; its internal edges are now left alone instead of walked
segment by segment to that result. Where a segment does need its
strength, two cells with the same motion from the same pictures are
recognised by two word compares instead of eight field compares.

About a third off the deblocking of a 480x272 B-picture stream, and a
twentieth off the whole decode. Output is unchanged:
the lossless samples, multi-slice cases among them, stay byte-exact
against FFmpeg.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HSDanAkwZ5QRTtfLqkEh68
2026-10-11 10:00:34 +02:00
libretroadminandClaude Opus 5.5 c0cd128541 rh264: CABAC renormalises without reading bits
The arithmetic decoder kept codIOffset as a nine-bit number and fed it
from a byte reader on every renormalisation, one call per decision
that renormalised and per bypass bin. It now keeps codIOffset in the
top of a 64-bit register with up to 55 bits of the stream below it: a
renormalisation, and a bypass bin's extra bit, are a count going down,
and the comparison against codIRange is made against codIRange shifted
to the same place. Bytes come in up to seven at a time when the count
runs low. I_PCM's return to the byte stream counts the prefetched bytes
back as before, and no longer counts the zeroes the reader makes up past
the end of a slice's data.

About a tenth off the decode of a 480x272 Main profile stream with B
pictures, CABAC throughout. Output is unchanged: the lossless samples
are still byte-exact against FFmpeg, I_PCM in CABAC streams included.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HSDanAkwZ5QRTtfLqkEh68
2026-10-11 10:00:30 +02:00
libretroadminandClaude Opus 5.5 66b52e0a01 raac: seven times the speed, and ahead of PPSSPP's FFmpeg
AAC-LC decoded at about a seventh of the time it took, which puts it
1.3 to 1.55 times as fast as the FFmpeg PPSSPP links (3.0.2, built
without its assembly, as the core ships it) on stereo and mono streams
at 44.1 and 48 kHz, and 1.3 times on HE-AAC:

 - bits come from one unaligned big-endian load a read instead of a
   byte at a time, and the spectral loop keeps the bits ahead in a
   register;
 - Huffman codes decode by a two-level table, built at open, whose
   entries carry the codebook's values already unpacked and the count
   of sign bits after them, instead of eight one-bit reads and a list
   scan; signs are applied without branches;
 - without pulse data the spectrum dequantises as it decodes, from a
   table of |q|^(4/3) and of scalefactor gains instead of a pow() per
   coefficient and per band;
 - the FFT behind every IMDCT and the SBR QMF reads its input in
   bit-reversed order in its first pass, and runs the rest as
   radix-2^2 butterflies four at a time against per-pass twiddle
   tables, on SSE2 or NEON chosen at compile time and in C otherwise;
   the twiddles were a running product before, and the output is
   closer to FFmpeg's for it (136 dB from 120);
 - the IMDCT's twiddles and scatter, the windows, M/S and the output
   conversion, s16 included, run four at a time;
 - the SBR QMF banks slide their history through a longer buffer and
   move it back once every 32 slots instead of every slot, and their
   window sums run four outputs at a time.

The output is checked against FFmpeg's for LC, mono, M/S, intensity,
PNS and TNS streams, against the previous decoder on HE-AAC and 7.1,
between the SSE2, NEON (aarch64 and armv7) and plain C paths, the s16
path against the float one, and under ASan and UBSan over mutated
packets.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HSDanAkwZ5QRTtfLqkEh68
2026-10-11 10:00:27 +02:00
libretroadminandClaude Opus 5.5 3bf283aca5 ratrac3: an ATRAC3 decoder, clean-room
ATRAC3 as PSP .at3 files and OpenMG carry it: stereo at 105 and 132
kb/s, joint stereo at 66, and mono. Written without reading any
decoder's source: the bit-stream syntax from public format notes and
the signal path measured, a sample at a time, against a reference
decoder fed streams crafted to isolate each step - the gain control
curve, tonal components and how far they carry the band count, the
joint stereo matrices and weights and how they ramp between frames.
The reference's quirks are kept where its output shows them; its
inverted polarity is not, so the output has the encoder's input
polarity, as PPSSPP's decoder's does.

The synthesis runs four lanes wide on SSE2 or NEON, chosen at compile
time, and in plain C otherwise: a channel's four band IMDCTs at once,
one a lane, through a 128-point FFT fed in bit-reversed order, and the
QMF tree sixteen output pairs a pass with each tap loaded once for the
sums and the differences. VLC coefficients come two to a lookup where
they fit, and the output is interleaved and converted four at a time.
Against PPSSPP's decoder, built the same way (-O2, SSE2), it decodes
three times as fast and matches its output to 133 dB.

The sample decodes a crafted stereo stream and a joint stereo one and
checks them against the reference's output, then the s16 output, the
fmt chunk parser, frames that fail to parse (silence, history kept),
reset, and 12000 frames of garbage and mutations under ASan and UBSan.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HSDanAkwZ5QRTtfLqkEh68
2026-10-11 10:00:24 +02:00
libretroadminandClaude Opus 5.5 85a5fa7d84 CI: rpsmf's sample runs with the other libretro-common samples
rpsmf_test was built by the samples job but listed in neither its run
nor its no-run targets, which the job treats as an error. It is a
self-contained test and runs under the job's ASan and UBSan build.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HSDanAkwZ5QRTtfLqkEh68
2026-10-11 10:00:20 +02:00
libretroadminandClaude Opus 5.5 5463261fc6 audio: the pipeline fill cap leaves a display-paced core alone
With the display pacing the core as well as Audio Sync - Display+Audio,
VSync+Audio, Scanline Sync - the fill swings by a publish with the
phase between the core's publish and the consumer's pass, and at one
publish over the target the cap now and then held the core for part of
a pass. The display pacing hold measures that as core time and
reserves it. Where the last iteration's pace had a display source the
cap is one publish higher, so only a core running ahead of the device
is held; where audio alone paces it stays where it was.

pipeline_clocked publishes the pace each lane runs under and fails a
cap that holds a display-paced core.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BbMMaa7pA2TMyXf47yP5jB
2026-10-11 09:59:50 +02:00
libretroadminandClaude Opus 5.5 cdc286f851 audio: the pipeline fill cap, with Audio Sync off too
With Audio Sync off the threaded pipeline keeps a whole device buffer
in the pipe as cushion against a late core, on top of the device's own:
about twice the Audio Latency setting queued. audio_pipeline_fill_cap
now gives that up for the same floors Audio Sync uses, one publish or
two without rate control. Off, nothing changes.

Display-paced, frames up to 16 ms late, queued ahead of each publish:
32 ms latency 71 -> 56 ms, 64 ms latency 143 -> 86 ms, with no short
pull the deep pipe does not also show. pipeline_clocked gains the
lane, run by the samples/audio workflow.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BbMMaa7pA2TMyXf47yP5jB
2026-10-11 02:37:19 +02:00
Codex 05c91ea309 gl2: Preserve const in texture upload helper
The texture load callback passes a const image. The upload helper only reads it, so accept a const pointer to fix the iOS 6 CI qualifier error.
2026-10-11 02:05:20 +02:00
libretroadmin 1dbf7ddf1a rmpeg1_ps, rpsmf: input parsed where it lies
rmpeg1_ps_borrow hands the demuxer input to parse in place rather than
copy in: packets whole within it point into it, and only a unit it ends
part way into is copied, to be completed from what is borrowed next.
rpsmf_borrow does the same for a PSP movie, and an ATRAC3plus frame
whole within one packet now goes out from where it lies too; only a
frame that runs across packets is assembled. A partly cut audio packet
is no longer copied at all: write and borrow take nothing until it is
used up.

On a 64 MB stream of 2048-byte packs, borrowed in sectors, the PSMF
path takes a third of the instructions PPSSPP's MpegDemux does for the
same frames and runs several times faster; written in, it still copies
each byte once, where MpegDemux copies a frame's 8 KB window per frame.
2026-10-11 02:00:47 +02:00
libretroadmin 9ceb37448f rpsmf: PSP movies demultiplexed, over MPEG-2 program streams
rmpeg1_ps now takes ISO/IEC 13818-1 program streams as well: an MPEG-2
pack header is read for its SCR and mux rate and its stuffing skipped,
and an MPEG-2 PES header gives its PTS and DTS, packet by packet, so a
DVD's VOB or a PSP movie demultiplexes as an MPEG-1 stream does.

rpsmf reads a PSMF's header and demultiplexes what follows: H.264 video
as its packets carried it, and ATRAC3plus audio cut into whole frames
by their 0FD0h headers across private stream 1 packets, behind each
packet's own header bytes - learnt from a substream's first packet and
held to every frame header after - with the timestamp of the packet a
frame starts in. A frame that does not chain is dropped and the stream
resynchronises on the next header. Its sample checks both against
program streams built for it, fed whole, a byte at a time and in odd
chunks.
2026-10-11 01:49:04 +02:00
LibretroAdmin c244d14cc1 d3d12: presets read a hardware frame where it lies in a larger texture
A version 2 core's texture stands in for frame.texture[0] in the
passes' descriptors when the preset keeps no history, through the rect
semantics in the corner of a larger texture, as on d3d11. The three
vec4s come from one slang_rect_values() for every driver.

The video_views core gains a d3d12 mode, and e2e/d3d.sh (was wine.sh)
takes the driver and runs natively from MSYS2 as well as under Wine.
2026-10-11 01:48:49 +02:00
libretroadmin e4efafa496 metal: planar frames converted on the GPU
The driver answers TEXTURE_GPU_FORMAT_YUV420 and converts planar frames
itself. An update copies the planes once into a staging slot of the
texture's, as an RGBA update does, and a compute kernel - compiled from
source the first time it is needed - converts them from there straight
into the RGBA8 texture the frontend is handed, on the blit command
buffer, in order with the draws around it. A slot the GPU is still
reading drops the frame. Each pixel takes the chroma its 2x2 block
shares, so the result is the CPU conversion's at any size. A kernel
that will not compile leaves planar frames to the CPU, and a planar
load under threaded video runs on the video thread, the one that owns
the blit command buffer.

The SDK stand-ins learn the calls it makes, and the harness's planar
lane holds metal to converting on the GPU.
2026-10-11 01:37:10 +02:00
libretroadminandClaude Opus 5.5 e9b59d7344 audio: an optional cap on the threaded pipeline's fill
With Audio Sync on and nothing else pacing the core (Vertical Sync
off, or a display that does not hold it), the producer is held only
by a full ring. The ring sits full, and every publish waits behind
all of it, on top of the device's buffer: about 170 ms queued at a
16 ms Audio Latency in samples/audio/pipeline_clocked, where a core
paced by the display queues 44 ms.

audio_pipeline_fill_cap ("Shallow Pipeline Queue", Audio >
Synchronization, off by default) holds the producer once the pipe
holds the rate-control target and one publish, in the same lock-free
wait on a consumer pass that a full ring uses. Priming is held under
the same line, or its wait could never end. Off, nothing changes.

Queued ahead of each publish, pipe and device, steady state:

  latency  audio-paced off/on    display-paced off/on
  16 ms    182 / 45 ms           44 / 44 ms
  32 ms    194 / 57 ms           58 / 49 ms
  64 ms    219 / 78 ms           91 / 54 ms

No more underruns with the cap than without, including a
display-paced core up to 10 ms late a frame (JITTER_US).

samples/audio/pipeline_clocked gains the audio-paced lane, and the
samples/audio workflow runs it: the cap has to halve the queue and
run clean.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BbMMaa7pA2TMyXf47yP5jB
2026-10-11 01:36:53 +02:00
libretroadmin 8fae7a6d64 d3d12: planar frames converted on the GPU
The driver answers TEXTURE_GPU_FORMAT_YUV420 and converts planar frames
itself. An update writes the planes from where they lie into the
texture's upload buffer, laid out as an R8 texture beside it - luma
above the chroma, Cb and Cr side by side or the interleaved plane as it
is - and the draw that next samples the texture records the copy into
that one and a compute shader that converts it into the RGBA texture
the frontend is handed, as a dirty texture's copy is recorded now. The
fence the in-place update already keeps drops a frame whose upload
buffer the GPU still reads. Each 2x2 block takes the chroma it shares,
so the result is the CPU conversion's at any size, in either chroma
order. The compute root signature takes sixteen constants for it; a
shader that will not compile leaves planar frames to the CPU.

The harness's planar lane holds d3d12 to converting on the GPU.
2026-10-11 01:26:21 +02:00
libretroadmin 258fa176bc d3d11: planar frames converted on the GPU
At feature level 11 the driver answers TEXTURE_GPU_FORMAT_YUV420 and
converts planar frames itself: the planes go up from where they lie
through UpdateSubresource into R8 and R8G8 textures beside the RGBA
texture the frontend is handed, and a compute shader, compiled on the
first one, writes the conversion into that texture through an
unordered-access view. Each 2x2 block takes the chroma it shares, so
the result is the CPU conversion's at any size, in either chroma
order; nothing stays bound once it has run. A shader that will not
compile turns the format off rather than failing every load.

The harness's planar lane holds d3d11 to converting on the GPU.
2026-10-11 01:13:56 +02:00
libretroadmin bedb96fd5b gl: planar frames converted on the GPU, gl and glcore
Both GL drivers now answer TEXTURE_GPU_FORMAT_YUV420 and convert planar
frames themselves, through one converter they share in gl_common
(gl_planar_*): the planes go up from where they lie into R8/RG8 - or
luminance and luminance-alpha where the context has no core profile -
row lengths covering any stride, a row at a time on GLES2 only, and a
fragment shader draws them into the RGBA texture the frontend is
handed. It samples the chroma a 2x2 block shares, so the result is the
CPU conversion's at any size, either chroma order. The driver's state
is put back as it was, and a program that will not build turns the
format off rather than failing every load.

The harness's planar lane holds gl and glcore to the CPU conversion as
it does Vulkan, and the compile matrix builds the converter for GLES2
and GLES3.
2026-10-11 00:28:33 +02:00
libretroadmin 5fc85a58d4 retro_spsc: a mirrored ring, so no span stops at the wrap
retro_spsc_init_mirrored() maps the ring's pages twice, back to back -
memfd on Linux and Android, an anonymous POSIX shm object on macOS and
the BSDs, a pagefile section on desktop Windows - so write_begin and
read_begin hand out every free or filled byte as one span, and a lend
is never cut short at the wrap. Positions wrap at the buffer's size,
which is at least a page or a Windows allocation granule, while the
capacity stays the power of two asked for: room, fill and every
latency bound read exactly as from retro_spsc_init. Elsewhere the ring
is a plain one. The begin calls also re-read the other side's cursor
when their private copy, rather than the wrap, limits the span, so a
stale copy no longer hands back less than is there.

The pipeline's ring and the rings WASAPI, WDM-KS, PulseAudio,
DirectSound and SDL2 lend from are mirrored. retro_spsc_test checks the
mirror's properties and streams spans through two threads with none
cut short, with the stale-cache check updated to the refresh; a lane
runs it on the shm path. live_drivers asserts Pulse's lends no longer
stop at the wrap.
2026-10-11 00:15:42 +02:00
libretroadmin c7c97ef5fc gfx: planar frames converted on the GPU, Vulkan first
A driver that answers TEXTURE_GPU_FORMAT_YUV420 is now handed a planar
surface's frame as it lies - struct texture_planar on the
texture_image, its planes, strides and colour space - and converts it
itself; gfx_surface converts on the CPU only for a driver that does
not, into a buffer it makes on the first such frame. Under the thread
wrapper the caller's planes are copied into a slot first, since the
video thread reads them after the submit returns; slots and direct
video go up from where they are. video_driver_texture_fit refuses a
planar frame the driver cannot take, and image_yuv_coefficients gives
GPUs the matrix the CPU conversion uses.

Vulkan copies the planes once into a staging buffer behind a small
header and converts them with a compute shader (yuv420_to_rgba8888)
straight into the RGBA8 texture, through the two-slot stream state the
in-place updates use: no intermediate images, a dropped frame rather
than a wait when the GPU is behind. The threaded-video harness reads
the texture back on lavapipe and holds it to the CPU conversion.
2026-10-10 23:42:33 +02:00
Acts1631 663a3b1eb9 libchdr: bound compressed hunk reads (#19796)
CHD map entries carry an attacker-controlled compressed length, but the
staging buffer is only one hunk large. A larger length makes the file
read overflow that heap allocation.

Reject lengths larger than the buffer before direct or cached reads.
2026-10-10 23:11:06 +02:00
Acts1631 fd662c56dd rchd: resize AVHUFF sample buffer between hunks (#19797)
AVHUFF hunks carry independent audio sample counts, but the temporary
sample buffer was allocated only for the first decoded hunk. A later
hunk with more samples caused the FLAC decoder to write beyond it.

Track the allocation capacity and grow it before decoding each hunk.
2026-10-10 23:09:30 +02:00
LibretroAdmin 438884e661 d3d11: presets read a hardware frame where it lies in a larger texture
slang_process() rewrites each pass to read the frame through the rect
semantics when the driver supplies them and the preset names a filter;
glcore and vulkan share the same slang_rect_remap_pass(). The
video_views core draws into the corner of a regular-maximum texture
with its large maximum, and wine.sh checks every rect preset on that
against the software frame.
2026-10-10 23:04:43 +02:00
libretroadmin 0cfb494aa6 ci: the threaded video frame's budget takes in the delivery it inlines
With the allocating loader gone, video_thread_async_deliver is small
enough that GCC inlines it into video_thread_frame, which runs it every
frame anyway: the call is gone and the frame is 56 bytes larger, two
past a budget measured from a reference the function had long outgrown.
The reference moves to what the frame measures now.
2026-10-10 23:04:43 +02:00
libretroadmin 16bed96cd0 audio: OpenSL, DirectSound, JACK and SDL2 lend to the output stage
OpenSL's blocks are one arena, so its lend runs from the fill point
through every block the device does not hold, to the arena's end: the
pipeline produces its output in the buffers the device plays, and the
end enqueues each block it completes, sharing write()'s enqueue.
DirectSound and SDL2 lend the ring their pump and callback drain, and
JACK the first part of its ringbuffer's write vector, whose two entry
points join the table it loads; each in whole frames.

opensl's mock now plays back what each block held when it finished,
and the lend's spans are checked to cross blocks and play in order.
dsound_pump lends through wine's DirectSound, jack_runtime through
the fake server into the port buffers, and sdl_lost_wakeup against
SDL's dummy device, which the samples/audio workflow now runs.
pipeline_wakeups reports the staged writes a lending device still
takes.
2026-10-10 23:04:43 +02:00
e4becf0cc4 rgui_config_directory: Rename 'Configuration files' label to Configurations and add Configuration Files, Overrides, and Manage Core Options to the sub-label (#19248)
* Rename 'Configuration files' label and add a correct and precise description to its sub-label

Fixes:
* https://github.com/libretro/RetroArch/issues/19200

* Update settings/settings_def_dir_core.h

Co-authored-by: Rob Loach <[email protected]>

* Update settings_def_dir_core.h

Restore Configuration Files, Overrides, and Manage Core Options without making the text longer.

This aligns with how other labels and sub-labels reference each other rather than using general descriptions. Note the capital 'File Browser' used specifically as a reference here: under Settings → User Interface → File Browser, the option Filter by current Core has the sub-label: 'Filter files being shown in File Browser by current core.'

* Update settings_def_dir_core.h

Corrected `Configuration Files` (plural) to `Configuration File` (singular)

* Update settings_def_dir_core.h

restore `sub-menus` from my previous revision

* settings: fix configurations directory description

Close the directory definition and clarify that Save Main Configuration writes to the active configuration path.

---------

Co-authored-by: Rob Loach <[email protected]>
Co-authored-by: Codex <[email protected]>
2026-10-10 23:05:34 +02:00
Noam Gilor 8bc6529e46 ozone: add Bright color theme (#17714)
Register Bright through the current string-based theme list. Use solid-color rendering because this theme has no texture assets.
2026-10-10 22:48:37 +02:00
Acts1631 11858630dd libchdr: reject oversubscribed Huffman trees (#19775)
A crafted CHDv5 map can assign more canonical Huffman codes than fit
at a given bit length. The lookup builder then uses the out-of-range
codes as indices and writes beyond its heap allocation.

Reject codes that exceed their declared bit-length namespace before
the lookup table is built.
2026-10-10 22:29:19 +02:00
Anthony SchemelandClaude Opus 5.5 b0dc5c2624 vita_pib: destroy the compiler output when a shader fails to compile (#19787)
pglPlatformShaderCompiler_CustomPatch called
sceShaccCgDestroyCompileOutput only when the compile produced program
data, so each failed compile leaked its output once the next compile
replaced the static pointer to it. The failed path now destroys it too.

Tests: on x86 under LeakSanitizer, a test includes shacccgpatch.c
against a stub of the shacccg compiler that returns an output with no
program data, and compiles twice. Before: a direct leak of the first
output. After: no leak. Not built with vitasdk or run on a Vita.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 22:27:17 +02:00
Anthony SchemelandClaude Opus 5.5 907804283f vita_pib: bound the shader log to its buffer and keep every diagnostic (#19786)
logShaccCg sprintf'd each compiler diagnostic, whose message has no
length limit, into the caller's 0x1024 stack buffer, and each one
overwrote the one before, so the log held only the last. It now takes
the buffer's size and appends each diagnostic with snprintf, stopping
when the buffer is full.

Tests: on x86 under ASan, a test includes shacccgpatch.c against a
stub of the shacccg types and calls logShaccCg with a 0x1024 stack
buffer. Before: a 6000-byte diagnostic is a stack-buffer-overflow, and
of two diagnostics only the second is kept. After: both are kept, and
the long one stops at 4131 bytes. Not built with vitasdk or run on a Vita.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 22:26:17 +02:00
libretroadmin e5a9026553 audio: PipeWire and CoreAudio lend their rings to the output stage
Both drivers front the device with a single-producer ring their own
callback drains - PipeWire's process callback, CoreAudio's render
callback - so both now lend the span at that ring's head, to its wrap
in whole frames, through write_begin/write_end, and the threaded
pipeline produces its output there instead of copying it in. The
PipeWire lend takes no thread-loop lock, which write() holds only for
its wait. A stream that is not streaming, a paused unit, and a frame
that would straddle the wrap keep write().

pipewire_write checks the lend against the ring across its wrap,
lock-free, and its refusals. coreaudio_write lends whole streams
through the shipping writer and render callback and plays them back
in order across the wrap; the samples/audio workflow now runs it.
2026-10-10 22:18:24 +02:00
Acts1631 7af3893f5d rpng: reject unsupported IHDR methods (#19776)
PNG only defines compression and filter method 0, and interlace
methods 0 and 1. Other nonzero interlace values selected Adam7
decoding while buffer setup used regular scanline geometry, preventing
the decoder from making progress on crafted files.

Validate all three method fields before calculating image geometry.
2026-10-10 22:19:18 +02:00
Codex ea99277ca4 ozone: explicitly discard pending-theme exchange results
Keep the atomic exchanges and their memory ordering unchanged while suppressing unused-value warnings in the GCC atomic backend.
2026-10-10 22:11:25 +02:00
libretroadmin 9beb5662c3 gfx_surface: planar surfaces, converted once where the planes lie
gfx_surface_new now takes I420 and NV12. A planar slot holds the
planes back to back; gfx_surface_submit_planes takes them wherever
they already are - a decoder's output, a camera buffer - with their
own strides and chroma step, so nothing is staged. Until a driver
samples YCbCr, the one pass over the frame is the BT.601/709
conversion into a buffer the surface owns, from which the upload
reads; the planes are the caller's again as soon as the submit
returns, and a planar slot is never lent the driver's memory, since
the texture holds the converted frame. gfx_surface_set_yuv names the
colour space. P010 waits for a 10-bit conversion.

This is the fallback every driver gets for free; the GPU YCbCr paths
that follow replace the conversion per driver without changing what
producers call.
2026-10-10 21:46:38 +02:00
libretroadmin da2955a3d5 gfx: one pixel-format vocabulary, from libretro-common
enum image_pixfmt now lives in formats/image.h and replaces the
gfx_surface-private enum, with I420, NV12 and P010 added alongside
their plane count, bytes-per-sample and frame-size helpers, so the
planar surfaces and the video player to come speak the same words as
the image decoders. No driver takes a planar texture yet;
gfx_surface_new still refuses one.

The decisions that were made three times are now made once: what to
ask the decoder for (gfx_surface_image_request, used by the image
task, the image set and the overlay loader in place of the overlay's
RGBA/10-bit loader flags) and whether the result fits the driver
(video_driver_texture_fit, which narrows 10-bit and refuses fp16 where
the driver lacks the format, for surfaces, overlays and
video_driver_texture_load alike).
2026-10-10 21:46:35 +02:00
libretroadmin 2904ca8050 audio: the threaded driver forwards the lend pair
The threaded pipeline runs the device behind the audio-thread wrapper,
and takes write_begin/write_end from the driver it sees - the wrapper,
which did not forward them, so no driver's lend was ever reached. It
forwards them now, as it does the device's other optional entries.

pipeline_wakeups gives its device a lend under LEND=1, and check-lend
runs it through the real wrapper, failing when no span reaches the
device; the audio-transport workflow runs it plain and under ASan.
2026-10-10 21:46:31 +02:00
Codex 643f01f112 hid: remove redundant Retrode report staging
Copy validated reports directly into the selected pad buffer. Remove the device staging buffer, saving one report copy and 64 bytes per device while preserving length and port validation.
2026-10-10 21:25:08 +02:00
libretroadmin 920980ac65 Update vfs_implementation.c 2026-10-10 21:22:40 +02:00
Acts1631 5de8ace4cb hid: validate Retrode input reports (#19777)
Retrode reports select a pad with their first byte and are copied into
fixed 64-byte buffers. A spoofed USB device could use an invalid port
or oversized report to copy attacker-controlled bytes outside the
allocated device object.

Reject null, short, oversized, and out-of-range reports before reading
the port or copying data.
2026-10-10 21:21:27 +02:00
64fcce996d Defer auto state load for cores requiring initialization (#19487)
* Defer auto state load for cores requiring initialization

* runloop: wait for deferred auto-state initialization

Keep the request pending until the primary core has run and reports a nonzero serialization size. Preserve one-shot loading without repeated state reads, reset execution tracking on lifecycle changes, and skip settings queries when no load is pending.

* tests: cover deferred auto-state readiness and cancellation

Drive the shipping runloop and command handler with a core that initializes over multiple frames. Check one-shot loading, idle overhead, cancellation, core reset, missing retro_run, paused frames, hardcore mode, and the unchanged non-quirk path. Add Linux sanitizer coverage and a Windows cross-build.

* runloop: avoid auto-state conflicts with master

Move deferred auto-state fields away from the lifecycle fields extended by master, and reset them after joining autosave workers. Keep the reset before core unloading so both the auto-state and audio teardown changes merge cleanly. Supply the new audio publication and between-frame polling dependencies in the regression harness.

---------

Co-authored-by: keycross <[email protected]>
Co-authored-by: Codex <[email protected]>
2026-10-10 21:15:37 +02:00
LibretroAdmin c04a778287 vulkan: views' presets read each view where it lies in the frame
The Vulkan filter chain rewrites its passes to read their frame as a
rectangle of the image bound there, as the glcore chain does, when
created with frame_rect; vulkan_filter_chain_texture says where the
frame lies in its image. A view's preset chain is built so, and is
handed the frame's image with the view's origin instead of a copy of
the view. Copies stay for a chain that cannot read in place, a core
image view of more than one level, and the headset's chains.

A chain rebuilt for a new swapchain keeps the stages it rewrote.
samples/gfx/vulkan_chain_alloc links slang_rect.c, which
shader_vulkan.c now calls. The video_views e2e runs the view rect
cases on Vulkan too: software frames and the core's own image.
2026-10-10 21:02:41 +02:00
libretroadmin a6965533ce audio: the ALSA mmap comment names the PCMs that only emulate it
dmix gives each client a buffer of its own and mixes it on commit, so
its mmap is not emulated; only ioplug and extplug PCMs are.
2026-10-10 20:44:53 +02:00
libretroadmin c3a13f7203 audio: ALSA lends a hw or plug device's own buffer to the output stage
With the threaded pipeline on, playback on a hw or plug PCM opens for
mmap access where the device offers it, and the driver lends the
contiguous span at the application pointer through write_begin and
write_end, so the pipeline's output is produced in the device's own
buffer instead of being copied there by snd_pcm_writei(). A commit
never starts the stream, so the end starts it at the threshold a
write would have. dmix, ioplug and extplug PCMs - the desktop's
default route through PulseAudio or PipeWire among them - emulate
their mmap buffer, and stay on read/write access, as does every
device with the pipeline off. An mmap PCM takes no snd_pcm_writei(),
so write() goes through snd_pcm_mmap_writei() there.

live_drivers drives a plug PCM over the null one through the lend,
pause and the pauseless stop included, checks which PCMs open for
mmap, and reads the lent bytes back in order through a file plugin.
2026-10-10 20:42:00 +02:00
Codex 4c968d01cc tests: check Android camera without generated host config
Both standalone camera lanes supply their Android feature defines explicitly. Do not require config.h, which a clean checkout does not contain and which would import unrelated host features into these target checks.
2026-10-10 20:30:09 +02:00
Codex 5321c58c4a image: include stddef for YUV plane offsets
Declare size_t directly instead of relying on a platform header to provide it. Add a PS2-profile C89 check to the compile matrix so the missing include cannot pass desktop-only coverage.
2026-10-10 20:30:09 +02:00
libretroadmin bd62d7b6dd gfx: every texture upload posted to the video thread is caller-owned
With badges on gfx_surface, nothing loads a texture through a node the
wrapper allocates: every post is a node embedded in the resource it
uploads for, delivered back through done() exactly once. The
allocating loader and its counters are gone, and the harness lanes
that drove it post caller-owned nodes the way gfx_surface does, with
the same contract asserted - the post returns at once, the upload runs
on the video thread, done() arrives on the main thread in post order,
teardown answers 0, and with no wrapper up the post takes nothing.
2026-10-10 20:28:30 +02:00
libretroadmin 31d7391947 audio: WDM-KS and PulseAudio lend their rings to the output stage
Both drivers front the device with a ring their own thread drains -
WDM-KS's refill thread into the mapped WaveRT loop, Pulse's server
thread into the stream - so both now lend the span at that ring's
head through write_begin/write_end, as WASAPI does, and the threaded
pipeline produces its output there instead of copying it in. A
WDM-KS integer pin is fed float of the same size, so its lent span
is converted in place on publish, which also retires the separate
conversion buffer's copy. Paused or not-ready Pulse streams, WDM-KS
packet pins, the AC-3 path and builds without threads keep write().

wdmks.c did not build without threads: the loop geometry it needs at
init sat inside the refill thread's guard. It is outside it now, and
compile-matrix builds the driver in that shape. wdmks_loop checks the
lend against the loop, the in-place conversion and every refusal;
live_drivers lends against a live Pulse server with the mainloop lock
counted.
2026-10-10 20:11:03 +02:00
Rob LoachandCodex ce5c333180 MCP: Options instead of toggles, correct error handling (#19731)
* tests: cover MCP option state and speed changes

Exercise the shipping option handlers and runloop setters with counted driver events. Cover recorder conflicts, hardcore activity, rejected speed changes and repeated requests without extra work. Run the harness under Linux sanitizers and on Windows in the network CI workflow.

* mcp: SET_OPTION/GET_OPTION, real errors

* mcp: apply option changes to runtime state

Reject recording and streaming conflicts before initializing a recorder. Read hardcore activity rather than its configured preference. Apply speed targets synchronously through runloop setters, sharing the fast-forward side effects with hotkey handling and respecting runtime restrictions.

---------

Co-authored-by: Codex <[email protected]>
2026-10-10 20:09:23 +02:00
LibretroAdmin 6f8b2a8245 glcore: views' presets read each view where it lies in the frame
A view is a rectangle of the core's frame. Its preset's chain is now
built to read it there, as a padded frame is read: the chain is handed
the frame's texture with the view's origin, and each view is no longer
copied out first. A chain that cannot read in place - a pass reading
the frame in a way that cannot be kept exact, or history - has the
views copied as before.

gl3_filter_chain_texture carries where the frame starts in its
texture, and a chain that copies a frame out honours it.
samples/gfx/gl3_shader_status links slang_rect.c, which shader_gl3.c
now calls.

The video_views e2e runs each view's rect preset against its twin that
reads a frame of history and so has the views copied: software frames
in XRGB8888 and RGB565, hardware frames counted from the bottom and
the top. The screenshots must match.
2026-10-10 19:49:25 +02:00
libretroadmin abd5bdcd3e samples: lend_write and image_yuv_blit build into their own obj/
Both new samples compiled their shared sources in place, which the
sample object directory check refuses.
2026-10-10 19:48:47 +02:00
libretroadmin 8042f59dbc camera: frames reach the core as planar YCbCr, in place
RETRO_CAMERA_BUFFER_PLANAR hands a core the capture device's own 4:2:0
planes, untouched, for the duration of the callback; the frame
callback sits past the end of retro_camera_callback and is read only
when its caps bit is set, so older cores are never read past.

The Android driver picks the cheapest delivery the core accepts: the
SurfaceTexture for a GL hardware context, the planes for a planar
core, and XRGB8888 converted once by image_yuv_blit only for a core
that takes nothing else. Planes come from Camera2 through the NDK
(resolved at runtime, the HAL buffer mapped where it lies) and fall
back to the legacy Camera's NV21 preview buffers, rotated between the
camera and the reader without a copy. No permission is declared: a
port that declares CAMERA gets the prompt, others get no camera.
2026-10-10 19:44:44 +02:00
libretroadmin 6f90262530 audio: a driver can lend its buffer to the output stage
The one copy every configuration still paid was write()'s own: the
finished mix, staged in the frontend's scratch, copied into whatever
buffer fronts the device. A driver whose front is such a buffer can
now expose a write_begin/write_end pair - a non-blocking lend of the
contiguous span at its head, published or abandoned by the end - and
the threaded pipeline, which has already waited for the room, points
the resampler, the unity passthrough's staging and the s16 narrowing
at the lent span instead, so the output is produced where the write
would have copied it. The bytes do not change, only where they are
made; a span the driver cuts short, a wider or virtualized device, an
AC-3 stream and every driver without the pair keep the staged write.
WASAPI lends the ring its pump drains, on both modes.

The lend_write sample runs the same input through a scripted driver
with and without the pair and compares the streams bit for bit, with
write() counted as forbidden while a lend is live, the identity
write's abandon covered, and short spans falling back; the
samples/audio workflow runs it plain and under ASan+UBSan.
2026-10-10 19:43:47 +02:00
libretroadmin 1e844d50a1 audio: the hot paths read their packed words once
The per-flush and per-batch paths loaded the packed atomic flags word
over and over - twenty-three acquire loads inside one flush - and a
word that can change between two of them hands the call an
inconsistent pair of bits: the mixer seen active at the mix and
inactive at the clamp leaves unclamped floats for the device. Each
hot call now takes one snapshot of the word and decides everything
from it, the same way it already treats the runloop snapshot; the
re-reads inside the producer's wait loop and the blocking write's
nonblock restore stay live, since seeing a change is their purpose.
The slowmotion ratio's published bits are loaded only under the
snapshot's slowmotion flag, which every use is already gated on.
2026-10-10 19:43:44 +02:00
libretroadmin acad5f2e2d audio: the threaded consumer renders the ring where it lies
The pass's chunk was copied out of pipe_ring into a bounce buffer
before the pipeline ran on it - a full extra pass over every chunk,
and for a wide pipe a dedicated allocation to land it in. The consumer
now renders each contiguous ring span in place and releases it as the
render returns, staging only a frame the physical wrap splits, the
same shape the transport's reader already uses; pipe_wide and its
allocation go away. A short device write parks its tail in
pipe_scratch - free on the stereo path - instead of pointing into the
span, and ends the pass so nothing renders over what the tail holds.

The ring space a span occupied returns to the producer when its render
completes rather than before it starts, so during a device stall the
producer meets the full ring one chunk sooner; its bounded wait and
drop already cover that, and the pass notify stays one.
2026-10-10 19:43:39 +02:00
libretroadmin afbad7a104 samples: shared sources build into each sample's own obj/
Samples compiled shared tree sources in place, so whichever sample ran
next linked those objects with the flags they were built with, as
unity_identity did with mic_handshake's ASan objects. Every sample that
builds a source from outside its directory now includes
sample_objdir.mk and maps those objects under its own obj/; its own
objects stay where they were. The rule for the mapped objects has a
slash in its target, so make 3.81 does not strip the directory.

tools/sample_objdir_check.py dry-runs every sample Makefile and fails
on an object written outside the sample; the Samples-Objdir-Check
workflow runs it.
2026-10-10 19:43:34 +02:00
77a0ea8a28 ozone: load the icon set of a color theme switched to on the fly (#19782)
* ozone: load the icon set of a color theme switched to on the fly

Only the theme current at a context reset gets its icon set
(cursor_noborder, cursor_static, check, switch) loaded. Switching the
color theme from the menu changes ozone->theme without a context
reset, so the new theme's slots stay NULL and every draw from them
gets texture 0. The display drivers substitute a blank white texture
for 0, so the cursor's nine-slice fill (cursor_alpha is pure white)
paints a solid white box over the selected entry until restart.

The frame path now marks the theme's textures pending when it
switches themes, and ozone_render() loads them on the main thread,
which owns texture submits. The reset frees the icon sets of other
themes, so switching back and forth doesn't pile them up. Until the
cursor texture is up, the cursor falls back to the quad-drawn
selection rather than drawing white.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* ozone: retain theme textures across color changes

Load each visited theme once per context and reuse its surfaces on later visits. Hand the requested theme to the main thread atomically and drain threaded frames before publishing new slots. Keep cached textures until context reset or destruction, with checkmark and keyboard cursor fallbacks while a first load is pending.

* ozone: omit unused Switch textures outside libnx

Compile the Switch footer texture slot and filename only where the footer can draw it. Other builds load three theme icons instead of four. Extend the ownership harness to check first-use loading, upload-free and eviction-free cached visits, and cache invalidation on context reset.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Codex <[email protected]>
2026-10-10 19:38:51 +02:00
Codex 5bff8fb1a1 samples: guard optional video filter GLSL test
Headless builds omit the shader loader. Compile the optional GLSL preset check only with HAVE_GLSL, and report an error if HARNESS_SHADER is requested without that support.
2026-10-10 19:16:50 +02:00
Codex 7df8e184b1 dxgi: fix swapchain factory query with legacy MSVC preprocessor
Select the C or C++ REFIID before invoking SUCCEEDED. The traditional MSVC preprocessor does not support conditional directives inside macro arguments, causing the ARM64 and UWP builds to fail after the flip-model swapchain change.

Keep the factory query, flip-model creation and legacy fallback behavior unchanged.
2026-10-10 19:09:08 +02:00
RyunamandCodex 91f4bbc33f OSD: Fix fastforward FPS count regression with some cores (#19790)
* OSD: Fix fastforward FPS count regression with some cores

* video: keep memory OSD updating across skipped frames

Track the last memory sample by frame count instead of requiring an exact interval multiple. Fast-forward cores that omit video callbacks can otherwise skip every refresh boundary. Reset the sample counter alongside the FPS counter.

---------

Co-authored-by: Codex <[email protected]>
2026-10-10 19:06:44 +02:00
Ryunam 8e6af903eb Video: In d3d11/12 use flip model to fix stretching when toggling screen modes (#19780) 2026-10-10 18:57:07 +02:00
Anthony SchemelandClaude Opus 5.5 614248a1c4 vulkan: let a streamed texture's first layout move cover transfer reads (#19789)
vulkan_create_texture moves a streamed texture from PREINITIALIZED to
GENERAL with a barrier whose second scope is fragment shader reads
only. A shader preset with frame history then copies the frame out of
that texture (slang_chain_update_history), and in GENERAL it records
no barrier of its own, so the copy was not ordered after the layout
transition. The barrier now covers transfer reads too.

Tests: the 2048 core with a two-pass slang preset reading
OriginalHistory1 and PassFeedback0, on lavapipe under the Khronos
validation layer with synchronization validation on. Before:
SYNC-HAZARD-READ-AFTER-WRITE twice, a vkCmdCopyImage reading the frame
texture after the vkCmdPipelineBarrier layout transition. After: none,
in two runs.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 18:50:14 +02:00
libretroadmin d7382b8532 cheevos: badges are gfx_surface stills
The badge cache decodes into a still surface and hands the surface
over, so every image-made texture in the tree is now a gfx_surface's
and the frontend's texture pokes have no caller outside it. A badge
that lands after its slot was reset or reused is freed by the load
itself, as are the surfaces of challenge indicators cleared or torn
down and of a popup dropped from a full ring. The Android camera
deletes the GL texture it made with GL.
2026-10-10 18:30:28 +02:00
libretroadmin ab86f3afe4 samples: unity_identity builds its own objects; gl3_shader_status links slang_rect
unity_identity compiled its shared sources in place, so its plain
build linked the ASan objects mic_handshake had just left in the tree.
Its objects now go to a directory of its own, removed by clean.

shader_gl3.c calls the slang_rect helpers, so the gl3_shader_status
sample builds slang_rect.c with it.
2026-10-10 18:28:32 +02:00
LibretroAdmin 533bb9116d slang: the rect names reach slang_process.c without glslang
slang_process.c lists SLANG_RECT_NAME_RECT, _CLAMP and _TEXELS in its
table of semantic uniform names, which every HAVE_SLANG build compiles,
but took slang_rect.h only under HAVE_GLSLANG. The Wii U build has
SPIRV-Cross and no glslang, so the names were undeclared there and the
build stopped at slang_process.c.

The header only declares names, types and prototypes, and slang_rect.c
is already built wherever slang_process.c is, so it is now included in
every build.
2026-10-10 18:09:29 +02:00
libretroadmin 92173ac201 audio: the unity passthrough keeps one buffer end to end
At a ratio pinned at exactly 1.0 the resampler is skipped, but the
batch was still staged around it: an int16 core's audio went through
s16->float, a copy and float->s16 on its way to an s16 device - three
passes over every batch for an identity - and the integer front pair
had no passthrough at all, paying its scalar sinc convolution for
unity output. The s16->float conversion now waits until the ratio is
known. A passthrough batch bound for an s16 device reaches the driver
as the core delivered it, still feeding the pause history, which is
true history at the output rate; a float device gets the one
conversion it needs, written straight into the output buffer; and the
integer front pair takes the same passthrough the float path has.
The mixer, the resume ramp, non-unity gain and rate control stay on
the staged path with the conversions they need, and where a pass is
removed the bytes that reach the device are unchanged.

The unity_identity sample counts the conversions through linker wraps
and compares the device's bytes to the core's, across the inline
path, the threaded pipeline, the integer front pair and each
fallback; the samples/audio workflow runs it plain and under
ASan+UBSan.
2026-10-10 17:31:59 +02:00
libretroadmin a114869564 samples/threaded_video: the 4K submit is timed on the wall clock on Windows
Windows charges thread CPU time in whole 15.625 ms ticks to whichever
thread holds the CPU when the tick fires, so a submit of microseconds
reads as a full tick often enough to push the mean past the budget.
There the submits are timed on the performance counter and the median
is checked; a copy still puts milliseconds on every submit.
2026-10-10 17:18:38 +02:00
libretroadmin 5dc5eb4d9e gfx: one signed flip from a draw's origin to the top-down y
VIDEO_POS_FLIP_Y gives the top-down y of a draw descriptor's element on
a surface of a given height, as an int whatever type the height comes
in, so an element reaching past the top lands at a small negative y.
Every display driver that flips the origin goes through it.

viewport_pack checks it against the float math it stands for, with the
height as unsigned, uint32_t and float. The threaded-video harness gains
a lane that draws a quad reaching above the screen from the menu frame,
unthreaded and under the wrapper, on whichever real driver it runs; on
Vulkan under the validation layer an unsigned flip there is
VUID-VkViewport-y-01776 and fails check-vulkan.
2026-10-10 17:14:27 +02:00
LibretroAdmin 89fa2bdaa7 glcore: presets read a core's frame where it lies
A hardware core's frame sits in the corner of a framebuffer sized to
its maximum. Each pass of a preset is now rewritten to read the frame
- Original, OriginalHistory0 and the first pass's Source - as a
rectangle of the texture bound there, as it would read a texture of
the frame alone, so the chain no longer copies the frame out.

gfx/drivers_shader/slang_rect.c does the rewrite on the stage's SPIR-V:
linear filtering with clamp to edge moves and clamps the coordinate;
nearest filtering, any wrap, fetches the texel the sampler would pick;
gathers fetch their four; texelFetch moves by the origin; textureSize
is the rectangle's. The rectangle goes in the push constants, or the
uniform block where those are full. A pass reading the frame in a way
that cannot be kept exact, or a preset keeping history, has the frame
copied as before.

samples/gfx/slang_rect runs every outcome in CI, cross-compiled to GLSL
and HLSL, and draws each rewritten stage with Mesa: bit for bit what
the stage it was reads from a texture of its own. The video_views e2e
runs presets on the core's padded frame and on its software frame.
2026-10-10 17:13:18 +02:00
libretroadmin c25c47642a samples/threaded_video: the grabbed-mouse lane counts with the video thread idle
The lane counts a poll's requests on the connection the video thread
shares. A frame still queued from before the polls can change the
window title on it - three properties - and those land in the count.
The video thread is drained once the mouse is grabbed, before the
first poll is counted.
2026-10-10 16:57:04 +02:00
libretroadmin 1446601db4 gfx: a menu element's flipped y is computed signed in every driver
Metal, D3D10 and GX2 turn a draw's bottom-up origin into a top-down y
as surface height minus y minus the element's height. The heights are
unsigned (MTLViewport's source dims, D3D10_VIEWPORT.Height, the GX2
surface and video dims), so an element reaching past the top produced
a value near 2^32 instead of a small negative one. Each of those sums
now runs in int or float, as the Vulkan driver's does, and GX2's
textured strip divides its x by the video width in float rather than
in unsigned integer arithmetic, which put every such vertex at 0 or 1.

The Vulkan mesh viewport gets the same signed treatment for a video
taller than the swapchain.
2026-10-10 15:51:52 +02:00
Anthony SchemelandClaude Opus 5.5 3eab59f633 vulkan: compute a menu element's viewport y as a signed value (#19788)
gfx_display_vk_draw computed the per-element viewport as swapchain
height minus the element's y minus its height, in unsigned arithmetic.
An element reaching past the top of the screen has a negative result
there, which wrapped to about 2^32. That is outside viewportBoundsRange,
so the viewport was invalid. The value is now computed signed; a
negative y is a valid viewport.

Tests: XMB on lavapipe under the Khronos validation layer, 25 seconds
in the menu. Before: VUID-VkViewport-y-01776 twenty times, with
y = 4294967296.0. After: no validation errors.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 15:10:19 +02:00
LibretroAdmin 514c33eea8 d3d11: presets read the frame where it is when they keep no history
A preset with no history reads its Original, and its first pass's
Source, straight from the frame: the core's D3D11 texture when it is
the frame's size with one level, or the dynamic texture a software
frame is written into. Nothing is copied into frame.texture[0]. A
preset that keeps history keeps the copy, which its history rotates
out of.

The video_views core gains a D3D11 mode on Windows (version 2, a
texture per sync index), and e2e/wine.sh runs its exact cases on d3d11
under Wine: software and D3D11 frames, stock, with pass.slangp and
with history.slangp, threaded and not.
2026-10-10 14:40:42 +02:00
LibretroAdmin eb12888eeb d3d11/d3d12: slang-only helpers are compiled with the slang passes
The HDR10 and inverse-tonemap setters and the deferred shader state's
free are only called from the slang passes; built without slang they
were defined and unused.
2026-10-10 13:49:39 +02:00
LibretroAdmin dec7188bac d3d12: lend software framebuffers in turn instead of draining the GPU
GET_CURRENT_SOFTWARE_FRAMEBUFFER drained the whole queue on every ask,
so each frame a core rendered into the loan waited for the GPU to go
idle. Two upload buffers are lent in turn: an ask gets the one the last
pushed frame is not in, so the cached frame stays where it is, and
waits only until the last frame that copied out of that buffer has
executed, which the frame between has normally seen to.

A size or format change drains the queue before the old buffers go:
they used to be released while a copy out of them could still be in
flight.
2026-10-10 13:44:04 +02:00
LibretroAdmin 2baf91184f d3d10/d3d11: stock software frames are drawn from where they are written
Without a preset, a software frame is written into a dynamic texture
mapped WRITE_DISCARD and drawn from it. The staging texture's map,
which waits while the GPU still copies the last frame out of it, and
the copy into the frame texture are both gone; a frame the GPU still
reads is renamed instead. Presets keep the staging path: their passes
read frame.texture[0].

The core keeps its own buffer, so the cached frame stays the core's.
2026-10-10 13:32:49 +02:00
LibretroAdmin 62178ad403 d3d11/d3d12: GPU screenshots read the frame without a recording
read_viewport serves both the recorder and screenshots. Outside a
recording it draws the frame again, copies its back buffer into the
recording ring - d3d11 before Present, in the frame; d3d12 with a list
of its own after it - and waits for that copy. Idle, the back buffer as
it stands is copied. The next frame not recorded frees the ring.

The threaded-video harness's GPU readback lane runs on d3d11 and d3d12
too, unthreaded and threaded.
2026-10-10 13:14:32 +02:00
LibretroAdmin 0167b47f57 video_thread: D3D11 version 1 frames are replayed before they are drawn
A version 1 D3D11 core under threaded video records into a deferred
context, so before its frame is drawn the video thread replays the
slot's command list and hands the driver its texture through
hw_ring_present_slot; hw_ring_install is for version 2 only. The D3D11
driver no longer reads RETRO_HW_FRAME_BUFFER_VALID as pixels when a
hardware frame arrives without a texture, as D3D10 and D3D12 already do.

samples/gfx/d3d11_ring_dispatch drives video_thread_hw.c against a
recording poke for both interface versions (mingw-w64, run under Wine
on Linux).
2026-10-10 13:08:29 +02:00
Codex 2cc7f3a037 input: relax payload-free WinRaw cursor requests 2026-10-10 12:43:32 +02:00
Codex 1ca2777dd7 input: publish WinRaw cursor policy atomically 2026-10-10 12:43:32 +02:00
LibretroAdmin 65d243043e glcore: stock frames are drawn straight from a larger texture
A core's GL framebuffer is as large as its maximum geometry, with the
frame in a corner. Through the stock chain the frame is drawn straight
from there, as views are, rather than copied into a texture of its own
first. Presets and rolling-scanline subframes keep the copy.

views_stock.frag is now stock_rect.frag: it draws any rectangle of a
texture as the stock chain draws a texture of its own.

The video_views e2e draws the core's frame whole out of its large
maximum, stock and with a preset, exactly and filtered.
2026-10-10 12:28:28 +02:00
LibretroAdmin 9438f794af vulkan: stock views are drawn straight from the frame
Without a preset, each view is drawn from the frame's image through its
own rectangle, with no copy into an image of its own. Presets, rolling-
scanline subframes and headset output still copy views out of the
frame.

views_stock.frag takes the view's first and last texel centres from its
uniform buffer, at full precision: vulkan's vertex colours are 16-bit.
2026-10-10 11:54:52 +02:00
Codex 26d96469d3 input: relax independent illuminance value publication 2026-10-10 11:54:40 +02:00
Craig 54b60d3f51 add strlcpy compat to openxr dep (#19785) 2026-10-10 11:35:29 +02:00
LibretroAdmin 79985b5e52 glcore: stock views are drawn straight from the frame
Without a preset, each view is drawn from the frame's texture through
its own rectangle, with no copy. views_stock.frag clamps sampling to
the view's first and last texel centres, so filtering stops at the
view's edge as it does at a texture's. Presets, and rolling-scanline
subframes, still copy views out of the frame for their chains.

The video_views e2e runs each exact case stock and with a passthrough
preset, whose screenshots must match, and compares the two filtered.
2026-10-10 11:27:53 +02:00
Codex 29ffedca12 input: discard absolute mouse reports superseded by cursor
When WinRaw takes a pending cursor query, clear the absolute notification it supersedes. Otherwise the next idle poll can replay an old absolute position. The discard uses a relaxed store: no payload is published and no extra read-modify-write is needed.

Factor the position-source drain into a private portable helper and test cursor priority, idle polls and reports arriving around the discard. Extend the shipping-driver fixture and track the helper in build dependencies and CI paths.
2026-10-10 11:04:10 +02:00
Davey Hughes b7aead9c2a glcore: views are copied with glCopyImageSubData where the GL has it (#19781)
A blit needs both textures renderable, so a frame in a format that
isn't can't be split into views and is drawn whole. glCopyImageSubData
copies raw texels between textures without a framebuffer, as
vkCmdCopyImage does on vulkan. GL 4.3 and ARB_copy_image have it;
elsewhere, and on GLES, views are still blitted. The log names which
the driver uses.

The video_views sample core can fill views with noise, a colour a pixel
by frame position, and send RGB565 software frames; its GL modes upload
a pattern into the core's framebuffer, so frames of both origins carry
one. e2e/run.py's exact cases draw the views unfiltered at an integer
scale, find each by its marker, and compare every pixel with its source
and the screens' order. glcore runs each again with Mesa hiding GL 4.3
and ARB_copy_image, so the views are blitted, and the two screenshots
must match.
2026-10-10 10:33:48 +02:00
LibretroAdmin 9e3729d598 samples/gfx/threaded_video: libdbus's own lock order is not a deadlock
The screensaver worker closes its private session-bus connection with
the connection lock held, and libdbus takes its I/O-path lock and the
connection lock the other way round inside a blocking call - both on
the worker, the one thread that uses that connection. ThreadSanitizer
reports the order as a possible deadlock, failing the gl1 lane; the
suppressions leave libdbus's internal order out, as they do the GL and
Vulkan libraries'. A deadlock in RetroArch's own locks still fails.
2026-10-10 10:04:02 +02:00
Craig c68a7869ad aarch64: enable openxr by default for steam frame users (#19784) 2026-10-10 10:03:17 +02:00
Codex 24b39c4640 input: publish OpenXR pointer settings as one word
Pack frontend-owned laser mode and menu-open state into one atomic configuration. Headset frames now read a coherent pair with one acquire load; both poll publication sites use one release store. Any-thread motor publication remains unchanged.

Extend the portable shipping-source fixture to force a publication between consumer reads and cover supported mode/menu pairs, yield and completed navigation polls.
2026-10-10 09:38:48 +02:00
LibretroAdmin 385892e8ce input: pass the port to input_joypad_analog_stick as unsigned
The poll loop's index is a size_t; the stick call takes the port as
unsigned, as input_state_wrap beside it already does.
2026-10-10 09:15:48 +02:00
Codex 765479b0ea input: reject invalid OpenXR rumble effects
Validate the effect before writing desired strength, so an unsupported effect cannot overwrite the weak motor. Cover invalid positive and negative effects and invalid ports in the desktop haptic fixture.
2026-10-10 09:11:06 +02:00
Codex efc2131d39 input: retry unaccepted OpenXR haptic requests
Keep the last accepted amplitude when apply or stop fails, including the positive XR_SESSION_NOT_FOCUSED result. Retry unchanged requests at the existing refresh interval; changed amplitudes and observed focus return proceed immediately. Retry state remains frontend-owned.

Add a portable fixture using the shipping desktop implementation and scripted runtime results, with sanitizer and MinGW CI coverage.
2026-10-10 09:11:06 +02:00
LibretroAdmin 52e9a0fa37 CI: run configure checks serially on the MSYS2 ARM64 lane
The CLANGARM64 lane sets QB_JOBS=1, so configure runs its checks in
place rather than in background workers under the emulated MSYS2
runtime. The x64 lanes keep the parallel checks.
2026-10-10 09:03:54 +02:00
Codex 640526d452 input: retry failed udev rumble gain writes
Only cache a gain after a successful event write. Keep failed gain requests on the existing writer deadline with a one-second backoff, process a changed gain immediately, and cancel retries when returning to the applied gain. Cover recovery and cancellation with and without threads.
2026-10-10 08:46:11 +02:00
Codex 2167e58d4d input: retry interrupted output writer wake writes
A pipe write interrupted before sending its byte can otherwise leave pending output asleep indefinitely. Retry EINTR, while retaining the nonblocking full-pipe coalescing behavior. Inject an interrupted first wake into the real writer regression.
2026-10-10 08:46:11 +02:00
LibretroAdmin c9a57df202 CI: drop Docker Hub credentials from job containers
Workflow validation rejects empty container credentials, and secrets
are empty on pull requests from forks, so job containers pull
anonymously again. PS4-ORBIS keeps its guarded login step.
2026-10-10 08:11:53 +02:00
github-actions 01b902b716 Fetch translations from Crowdin 2026-10-10 00:27:10 +00:00
LibretroAdmin cb1b37a8c7 CI: authenticate Docker Hub pulls
Container jobs pull with the DOCKERHUB_USERNAME/DOCKERHUB_TOKEN
repository secrets so they count against the account's limit instead
of the shared per-IP anonymous limit of the hosted runners. Without
the secrets, as on fork pull requests, pulls stay anonymous.
2026-10-09 23:33:34 +02:00
LibretroAdmin 3b10e5ff35 griffin_objc: include TargetConditionals.h before testing TARGET_OS_*
The Availability header selection at the top of the unity TU tests
TARGET_OS_IPHONE, so TargetConditionals.h is included first to keep
-Wundef-prefix=TARGET_OS_ quiet.
2026-10-09 22:55:29 +02:00
Codex 9e85b8ecc6 input: remove the unused atomic udev rumble write counter
No production or regression code reads this counter. Remove its atomic increments from effect upload and playback; regression traces already observe the actual ioctl and event writes.
2026-10-09 21:56:07 +02:00
Codex fcb6b13b54 input: refresh udev rumble effects and retry failed writes
Use the documented 32767 ms replay limit and restart active effects every 30 seconds on the output writer. Keep failed uploads and plays pending with a one-second backoff. Without a writer, service cached deadlines from poll. Exercise unchanged refresh, failed starts and stops, cancellation and active-effect hotplug.
2026-10-09 21:56:07 +02:00
Codex 9546695660 input: allow output writers to service driver-owned deadlines
Add an optional writer-thread timeout callback without adding shared timer state. Existing untimed POSIX writers retain their blocking read path. Cover timed service, idle waits, wake interruption, shutdown and no-thread fallback with a portable regression.
2026-10-09 21:56:07 +02:00
Codex 3fe19e7acf input: publish udev rumble motor strengths as one atomic pair
Build the full 16-bit motor pair from the frontend-owned strength cache. The output writer captures both with one load, and lifecycle resets clear them with one store. Preserve independent motor changes, full-strength precision and threaded/non-threaded behavior in the real-driver regression.
2026-10-09 21:16:05 +02:00
Codex dcc0aa984a input: pack libusb removal and active sender count
Claim a sender and observe removal in one RMW, then identify the last removed sender from its release RMW. Preserve the output-owner handshake and cancellation policy while eliminating two separate removal loads per send_control call.
2026-10-09 21:00:59 +02:00
Codex 1560959467 input: pack HID connection admission and reader count
Order reader admission and retirement through one atomic word. This removes the cross-word sequence fences and live loads from each connection read. Preserve rejected readers during slot publication with fetch-or rather than overwriting their count. Add portable paused-reader and churn regressions with sanitizer CI coverage.
2026-10-09 21:00:59 +02:00
sonninnos 9e80d789b8 Reorder Video + Input settings 2026-10-09 21:44:58 +03:00
sonninnos 3d18e5ada2 Use common OFF visual label for input options 2026-10-09 21:44:57 +03:00
Codex b9324481cd input: avoid clearing already idle keyboard masks
Track whether the cached bind mask actually contains held keys. Clear it only after a held poll, while preserving release and bind-removal behavior. Extend the shipping frontend lane across idle, repress and removal.
2026-10-09 20:39:59 +02:00
Codex 7e0395a0b7 input: wrap sensor snapshot sequences with unsigned arithmetic
Keep both publication increments defined across the signed boundary and the full counter lap. Exercise both boundaries through the shipping poll and snapshot reader.
2026-10-09 20:39:55 +02:00
Codex 8e21535278 input: keep main-thread keys behind outstanding queue claims
Drain the claims present at entry before direct delivery, and queue behind an unpublished claim without waiting. Preserve immediate delivery for an empty lane. Cover queued/direct reversals, paused and full writers, mixed producer/reader traffic, and posts arriving during delivery. Main-thread events blocked behind a full queue use the existing bounded overflow policy.
2026-10-09 20:16:14 +02:00
Codex 5d022d0029 input: order overflow key releases after their claimed presses
Retain a release with its queue cutoff instead of letting a delayed press clear its bitmap bit. The reader waits for that cutoff and ignores recovery superseded by a newer delivered press. Extend reader positions without requiring 64-bit atomics. Cover paused presses, delayed release publication, counter wrap and long-held keys.
2026-10-09 20:16:10 +02:00
Codex e42ca073aa input: visit only posted output slots
Replace the post counter and historical high-water mark with a pending-slot mask. Keep the idle load-only check and each packed value mailbox, while avoiding exchanges on unused output slots and counter-wrap aliasing. Exercise sparse boundary slots and posts racing notification capture.
2026-10-09 19:51:02 +02:00
LibretroAdmin 8e294307cb samples/gfx/video_filter_menu: widen32 writes both output rows itself
The second row of each doubled pair was a memcpy from the first, within
the one output buffer, which the overlap copy check refuses. Each pixel
now goes to both rows as it is made.
2026-10-09 19:33:49 +02:00
Codex 0ab3ac8862 winusb: let the reader cancel its submitted read on shutdown
Wake the reader with a persistent stop event instead of aborting from the closing thread. The reader waits for completion or stop and aborts only after submitting its operation, closing the abort-before-submit race.

Model pending overlapped reads in the WinUSB stand-in and force shutdown while submission is paused. Run the harness under Wine in input CI. Cancellation completion still relies on the driver honoring AbortPipe.
2026-10-09 19:26:13 +02:00
Codex 421e7d9859 input: publish output values and pending state atomically
Pack a 16-bit rumble strength or boolean LED state with its pending bit in one 32-bit mailbox. Taking a slot atomically consumes its value, so a concurrent post cannot replace a taken value and be delivered twice.

Keep repeated-value refreshes and zero-strength stops. Normalize LED states at the boolean libretro boundary. Add a forced take/post race and shared-slot producer coverage.
2026-10-09 19:26:13 +02:00
Codex 0be66d901d input: preserve pressure suppressed by analog shaping
Only a zero raw pull may use the digital-button fallback. A nonzero pull shaped to zero by the deadzone or sensitivity must remain zero. Preserve the existing fallback for zero raw pressure and digital-only binds.

Extend the frontend pressure lane with held digital buttons below and above the deadzone, and with zero sensitivity.
2026-10-09 19:26:13 +02:00
LibretroAdmin 9358862eca core info: the running core's information shows again
drivers_init() made the current-core entry anew, blank, every time; the
core's probe in driver and menu init then filled it in again. With the
core opened once, the probe is skipped while a core runs, so the entry
stayed blank: no Core Information in the Information menu, and "No Core
Information Available" from the Quick Menu. A running core's entry now
stays; one freed with the drivers is made anew, and
CMD_EVENT_CORE_INFO_INIT fills it in when it rebuilds the list.

samples/menu/core_info_page boots a core with an info file and checks
the current-core entry, the Information menu and the Core Information
page, once the core is up and again after a driver reinit; it runs in
Linux-menu-harness.yml.

Two of this week's changes did not build where CI builds them: the
setting_command harness used the mobile overlay defaults, which
file_path_special.h defines on mobile only, and the Metal driver
CGSizeZero, which the compile matrix's SDK stand-ins do not declare.
2026-10-09 19:03:09 +02:00
LibretroAdmin 800147cb14 metal: a software filter change is taken in place
The frame texture is 32-bit whatever the source, a 16-bit source being
converted into it from a staging texture, and the frames' size is taken
per frame already. set_frame_format gives the frame view the new source
format, which makes the staging texture again or drops it; the
software framebuffer lent to a core is made again at its next lend, its
stride being the new format's. A 10-bit source is still set up again.

samples/gfx/video_filter_menu builds on macOS as threaded_video does,
keeping Cocoa's main and running from rarch_main() with its
harness_cocoa.m, so HARNESS_VIDEO_DRIVER=metal runs the steps there.
Neither has been built or run here: there is no Apple SDK to build
with.
2026-10-09 18:11:48 +02:00
Poly 091f178b26 ios: a URL that launches the app opens it (#19774)
With scenes, the URL that cold-starts the app - a retroarch:// deep
link, a TopShelf entry, a file opened in RetroArch - comes in the
scene's connection options, not through scene:openURLContexts:, and
was dropped. The scene delegate now hands those to the app's openURL
handler as it does the ones that arrive while running; RetroArch is up
by then, applicationDidFinishLaunching having run rarch_main.

scene:openURLContexts: passes the UIApplication rather than the
RetroArch_iOS object cast to one.
2026-10-09 18:02:43 +02:00
Acts1631 9db9cf3f45 h265: reject oversized Exp-Golomb values (#19773)
rh265_ue() could return values above INT_MAX, up to UINT32_MAX, for
oversized ue(v) codes. The SPS, PPS and slice header parsers cast them
to int before their upper-bound checks, so 2^32 - 1 read as -1 and
passed: a crafted SPS reached memcpy(&v->sps[id], ...) with a negative
id, a heap buffer underflow while opening H.265 input such as an MP4.
The PPS and slice paths had the same negative indices.

No ue(v) field the decoder reads goes past 2^25 - 2, so a longer code
now marks the unit malformed, which the parsers' completion check
already refuses before anything is stored. That bound also keeps the
sums the parsers make of these values (bit depth + 8, the CTB sizes,
the scaling list reference, the RPS deltas, the slice QP) from
overflowing int. sps_max_latency_increase_plus1, which the spec lets
run to 2^32 - 2 and nothing reads, is skipped at any length.

samples/formats/h265/rh265_malformed_test feeds crafted SPSs through
hvcC extradata: an sps_id of 32 zero bits or of 2^32 - 2 and a width of
2^25 - 1 are refused, a latency of 2^32 - 2 still parses.
2026-10-09 17:59:30 +02:00
Jamiras df16ef193c config_file: values may hold double quotes (#19218)
A value is written with each double quote escaped as \", and a run of
backslashes before a quote, or at the end of the value, doubled, so the
run cannot be read as escaping the quote after it. Other backslashes -
a Windows path - stay single. Reading undoes it: a quote after an odd
run of backslashes is part of the value. A line with every quote
escaped was written before values were escaped - a directory ending in
a backslash - and its first quote still closes it.

Parsing a value without a backslash before its closing quote costs what
it did; the escaped case is kept out of line. Writing a value scans it
once for quotes.

samples/file/config_file: every value up to five characters over
a, ", \, #, space and / is written and read back the same, with a
comment holding quotes after it too; the escaped forms, and lines
written before escaping, parse as they should.

Fixes #15848
2026-10-09 14:19:40 +02:00
Artem Lytkin 83ade2d959 overlay: resetting a preset restores the default overlay on mobile (#19615)
On mobile, config loading fills in the neo-retropad overlay and the
US-101 keyboard when no preset is set, but Start on Overlay Preset or
OSK Overlay Preset cleared the path, leaving touch-only devices with no
on-screen controls until the next restart. Both rows now take a Start
action that restores what a fresh configuration has: the bundled preset
under the overlay directory on mobile, no preset elsewhere.

samples/menu/setting_command presses Start on both rows, and on the OSK
opacity row beside them. CI runs the desktop half; a tree built with
CFLAGS=-DRARCH_MOBILE runs the mobile half.

Fixes #19474
2026-10-09 13:50:45 +02:00
LibretroAdmin 919aaef752 gl: a shader that needs more textures makes PSGL's reference buffer again
gl2_set_shader deletes the texture reference buffer and gl2_init_textures
generates a new one only when gl->pbo is 0; it is cleared after the
delete, so the rebuilt textures reference a live buffer rather than a
deleted name. PSGL only; not built or run here.
2026-10-09 13:46:34 +02:00
LibretroAdmin 60eca9f93e d3d11, d3d12: a software filter change is taken in place
set_frame_format puts the new format in the frame texture's
description and lets the next frame make it again, as a new size does,
with the history textures after it. Hardware rendering and a 10-bit
source are still set up again.

On gl, PSGL takes it as well: gl2_init_textures sizes the texture
reference buffer again for the new textures.

samples/gfx/video_filter_menu builds against a MinGW tree, where the
binary is linked as .exe.
2026-10-09 13:12:29 +02:00
LibretroAdmin 64d1a4b138 gl: a software filter change is taken in place
set_frame_format makes the frame textures again in the new format, and
at the new size when it is larger than theirs, with the render chain's
passes around them; empty_buf and conv_buffer grow with them. Hardware
rendering, a 10-bit source and PSGL are still set up again.

samples/gfx/video_filter_menu runs on gl as on glcore and Vulkan, and
HARNESS_SHADER=1 runs its steps with a two-pass GLSL preset
(twopass.glslp), so the passes are made again around the new textures:
every step taken in place, the screen read back as the driver set up
again draws it, threaded and not.
2026-10-09 12:48:20 +02:00
LibretroAdmin 2d4930b338 video: a software filter change is taken by the driver in place
Choosing, removing or turning off and on a CPU video filter changes the
frames the driver gets: their pixel format and largest size. A new
optional poke, set_frame_format, tells the driver; one that adapts in
place returns true and keeps running, and the frontend sets up again
only a driver that does not have it or declines. A change the frames
do not make - a smaller scale, the same format - tells it nothing.

video_driver_filter_apply() is the one place this happens: the Video
Filter and Video Filter Enable rows, Remove Video Filter and the toggle
hotkey all go through it, and the driver's init records what it was set
up for from the same reckoning.

glcore and Vulkan take it in place: their streamed textures already
follow the frame's size, and now its format. The threaded video wrapper
forwards it from the video thread, growing its frame slots first when
the new frames need more room.

samples/gfx/video_filter_menu: with the null driver, a driver taking
the change in place (a stand-in on the poke table) is told each change
once with what the frames now are and set up again for none, and the
null driver itself is set up again only for a new format or a larger
scale. With HARNESS_VIDEO_DRIVER=glcore or vulkan under a display,
threaded and not, every step is taken in place and the screen read
back matches the driver set up again, including a format change at the
same frame size (Normal2x and widen32, a 32-bit test filter).
2026-10-09 12:29:49 +02:00
LibretroAdmin 76b7423ba5 file_list: sorting an empty or one-entry list is a no-op
file_list_sort_on_alt() and file_list_sort_on_type() leave a list of
fewer than two entries alone, so an empty list's NULL storage never
reaches qsort(). An empty manifest, as the cloudsync pacing harness's
fetch lane commits, is sorted without tripping UBSan.
2026-10-09 12:22:17 +02:00
libretroadmin 88f08e3b60 microphone: build only the resampler the flush uses
The flush passes samples straight through when the device runs at the
core's rate, and takes the integer path for an int16 device whose
backend has one; the float resampler serves only float devices and
backends without an integer implementation. The format and both rates
are fixed at open, so the microphone now builds the one resampler, or
none, that its flush will use, instead of a float one every time.

mic_handshake checks the choice through the real open - none at a
matching rate, the integer one for int16, the float one for float - and
that the integer-only microphone still delivers, and now also runs under
ASan and UBSan in CI.
2026-10-09 12:19:00 +02:00
Zhenyu Qi ce362265c6 menu: the video filter rows set video up for what it is then given (#19732)
Choosing a CPU video filter while content runs sets the video driver up
again, as removing one does: the driver was set up for the core's frame,
not the filter's scale and pixel format, so gl showed the last
unfiltered frame and ntsc_crt came out garbled on gl, glcore and vulkan.

Video Filter Enable in the menu now does what the toggle hotkey does:
the driver is set up again when the loaded filter outputs another pixel
format than the core's (ntsc_crt), and the toggle stays instant when it
does not.

samples/gfx/video_filter_menu drives both rows through the menu's own
entry points on a 240x160 RGB565 core with Normal4x and ntsc_crt, and
runs in Linux-menu-harness.yml.
2026-10-09 12:00:23 +02:00
libretroadmin 922a60b2e2 strlcyp can do the job here 2026-10-09 11:55:45 +02:00
Craig d5948f9b0b Add OpenXR to system information and log (#19771) 2026-10-09 11:58:05 +02:00
libretroadmin 268d3cccbb pipeline_wakeups: the consumer-exit lane runs its race and nothing else
CONSUMER_EXIT is the pipe_consumer_gone race: a producer parked on the
ring while the wrapper, its device refusing, leaves its loop. Each run
now starts with the device taking writes again, so every run reaches
that park rather than only the first, and the lane fails if the
producer never parked. Like the pause boundary, it runs only against a
device that pushes back - without it the producer never waits - and
skips the restart cycles and conversion invariants of the load sweep,
which need a consumer that stays.

The lane joins the transport workflow in both legs and, built for
ThreadSanitizer, last in the plain one; storing pipe_consumer_gone
plainly again makes that step report the race.
2026-10-09 11:54:01 +02:00
Anthony Schemel abd42c0993 net/tls: show an on-screen notice for refused and unverified connections (#19712)
A refused certificate or a connection made with verification disabled
now shows an on-screen notice alongside the log line: the refusal names
the 'TLS Certificate Verification' setting, the disabled notice says
connections are not being checked. Background tasks can connect many
times a minute, so each notice shows once a session, guarded by an
atomic since the hooks run on connection threads. Optional mode stays
log-only.

The BearSSL backend now reports a handshake that ended on the
certificate through ssl_socket_log_verify_fail, as the built-in client
and mbedTLS do, and ships the weak no-op hooks.

samples/network/tls_retro/verify_hook_test.sh checks that a refused
certificate reaches the hook on each backend; it runs in the built-in
client's local server matrix and in the mbedTLS and BearSSL lanes.
2026-10-09 11:35:31 +02:00
Anthony SchemelandClaude Opus 5.5 b636e545b7 platform_unix: async-signal-safe exit on the second quit signal (#19664)
The second SIGINT/SIGTERM called exit(1) from the signal handler.
exit() runs atexit handlers and frees memory; when the signal lands
inside free() during a slow shutdown, it deadlocks on the allocator's
lock, so the force-quit hangs instead of quitting. Seen twice under
perf (core dump: frontend_unix_sighandler -> exit -> _int_free, the
interrupted frame being core_info_free in driver_uninit).

Repro (null video, second SIGINT sent when the log says "Unloading
core..."): 8 hangs in 20 runs before, 0 in 20 after. Builds clean,
normal and C89_BUILD=1.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 11:18:30 +02:00
Anthony Schemel cf77d2553f runahead: copy the core only into a private temp dir (#19660) 2026-10-09 11:18:16 +02:00
LibretroAdmin 6432c7ee99 drm, ctr: a freed surface takes its buffers with it; unused locals go
A DRM surface unmaps, removes and destroys each page's dumb buffer
when freed, and frees the page mutexes. A core's new resolution moves
the plane to the new surface before the old one goes, so the
framebuffer being scanned out is never removed from under the plane.
The plane queries free the libdrm objects they take.

ctr_gfx.c declares the console log's counters and the old libctru's
framebuffer externs only where they are used, and drops the rest.
2026-10-09 11:04:35 +02:00
libretroadmin edb8c2fe21 pipeline_wakeups: wait on the consumer's progress, not a wall clock
The harness waits - the control drain, the fallback drain, the restart's
first write, the pause boundary's discard and its resumed tap - each
gave the consumer a fixed one or two seconds, which a loaded runner
outlasts while the pipeline is still moving. They now wake on every
device write and pull through an eventcount and fail only after three
seconds with no device write and no ring drain, or a minute in all; a
departed consumer still fails them, at much the speed it did.

The pause boundary target joins the transport workflow, which never ran
it.
2026-10-09 10:55:52 +02:00
Anthony SchemelandClaude Opus 5.5 b1ae52df21 drm_gfx: fail when a dumb framebuffer cannot be made (#19770)
modeset_create_dumbfb ignored the results of its three ioctls and
returned 0 even when mmap failed, and both callers only logged a
non-zero return. A failed mmap left the page's map at MAP_FAILED, and
the next frame's blit wrote through it. Check each step, undo what was
made when one fails, and return -1. drm_surface_setup now returns
bool and frees the surface on failure: drm_frame returns false, which
stops the video driver, and the menu path skips the frame. init_drm
fails, so the driver does not start.

Tests: an ASan+UBSan harness that includes drm_gfx.c, fakes drmIoctl
and wraps mmap. Before: a failure of mmap, CREATE_DUMB, ADDFB or
MAP_DUMB returns 0 and the surface is kept, with page 0 at MAP_FAILED
for mmap. After: each returns -1, removes the framebuffer and destroys
the buffer as far as they were made, and the surface is NULL. Not run
on DRM hardware.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 10:54:29 +02:00
Anthony SchemelandClaude Opus 5.5 fbb33144c7 qt: FloatSlider compares its value as a float when painting (#19769)
paintEvent compared value() / m_precision, an unsigned integer
division, with the float setting. For any fractional value the
quotient truncated (35 / 100 is 0, never 0.35), so every repaint
re-set the slider from the setting, and a negative slider value was
converted to unsigned first. Divide as onValueChanged does.

Tests: build only, with HAVE_QT; the effect is a re-set value on a
live slider's repaint rather than a fault.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 10:54:16 +02:00
Anthony SchemelandClaude Opus 5.5 27f9bd9565 platform_unix: read the /proc/acpi battery capacities (#19768)
check_proc_acpi_battery tested endptr before strtol had set it, so
neither "remaining capacity" nor "design capacity" was ever parsed and
the legacy /proc/acpi battery path never reported a percentage. The
"endptr &&" came from a Coverity cleanup (72dc03a1c6) that hid the
order rather than fixing it. Parse first, then check that a number was
read and a space follows. With the parse live, a design capacity of 0
would divide by zero, so require maximum > 0.

Tests: an ASan+UBSan harness that includes platform_unix.c and serves
fake state and info files. Before: 3000 of 4000 mAh reads percent -1.
After: 75. A design capacity of 0 reads -1.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 10:54:04 +02:00
Anthony SchemelandClaude Opus 5.5 07e2c48de5 xmb: an out-of-range animation setting takes the default (#19767)
The switches on menu_xmb_animation_horizontal_highlight and
menu_xmb_animation_opening_main_menu had no default, and the stack
animation entry they fill is not initialised. Neither setting is
range-checked when the config file loads, so a hand-edited value
outside the menu's choices pushed an animation with a garbage
duration and easing. Fall back to case 0, which is
DEFAULT_XMB_ANIMATION for both.

Tests: build only; reaching the switch needs the XMB menu animating,
which a headless run does not drive.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 10:53:37 +02:00
Anthony SchemelandClaude Opus 5.5 5d6dbddf94 winmm_midi: unprepare the earlier buffers when one fails (#19766)
When midiOutPrepareHeader failed on buffer i, the unwind loop
"while (--i <= 0)" ran on an unsigned i: a failure on buffer 2 left
buffers 0 and 1 prepared, and a failure on buffer 1 unprepared buffer
0 and stopped only because i wrapped. Count down with i-- > 0.

Tests: under wine, with a harness that includes winmm_midi.c and fakes
the two header calls. Before: a failure on the last buffer gives
prepared 2, unprepared 0. After: a failure on buffer 0, 1 or 2
unprepares exactly what was prepared. Cross-compiled with
x86_64-w64-mingw32-gcc -Wall -Wsign-compare, no warnings.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 10:53:22 +02:00
Anthony SchemelandClaude Opus 5.5 da111da0ca ntsc filter: clamp the left-edge taps with signed arithmetic (#19765)
ntsc_process_line clamped each chroma tap with "x + t < 0", but x is
unsigned, so the test never fired. At the left edge x + t wrapped, the
upper clamp took it to the last sample of the line, and the right
edge's colour bled onto the left edge of every scanline. The wrapped
sum also gave those taps the wrong phase whenever the phase count does
not divide 2^32, which the default 12 does not. Take the tap position
as a signed int, clamp it, and fold a negative phase back into range.

Tests: an ASan+UBSan harness that runs ntsc_create and ntsc_packets on
one line, black but for four white pixels on the right. Before: the
left-most six output pixels are purple and blue. After: all black,
right edge unchanged. Without the phase fold, UBSan reports index -6
into lut_cos. ntsc.so builds with -Wall -Wsign-compare and no
warnings, against 2 before.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 10:53:10 +02:00
Anthony SchemelandClaude Opus 5.5 d7b8c48e87 modeline: refuse a user modeline with a zero total (#19764)
modeline_parse checked only that sscanf read nine numbers, then
divided the pixel clock by htotal (integer division) and hfreq by
vtotal. A user modeline with htotal 0 crashed RetroArch with SIGFPE
when modeline generation was on; vtotal 0 gave an infinite vfreq that
was then cast to int. Refuse either, as a missing parameter is.

Tests: an ASan+UBSan harness that includes modeline_core.c. Before:
htotal 0 is "division by zero" and an FPE, vtotal 0 gives vfreq inf.
After: both return 0 with the error logged, and a valid 640x480 line
still parses to 59.94 Hz.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 10:52:54 +02:00
Anthony SchemelandClaude Opus 5.5 a5843fd4bf task_screenshot: rotate by pixel, not by uint32_t (#19763)
screenshot_rotate indexed its buffers as uint32_t by pixel number and
ran x across the pitch in bytes, bounded only by "> size / bpp". A
2-byte-per-pixel frame from a rotating core therefore wrote up to 4N
bytes into the 2N-byte buffer take_screenshot_raw allocates, and a
4-byte frame wrote element N, one past the end. Copy bpp bytes per
pixel, run x across the width, and bound the indices with >=.

Tests: an ASan harness that includes task_screenshot.c and calls
screenshot_rotate as take_screenshot_raw does. Before: a
heap-buffer-overflow at bpp 2 and bpp 4 (rotation 1). After: all four
rotations clean at both, and each output is the exact rotation of the
input with every pixel's bytes intact.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 10:52:38 +02:00
LibretroAdmin 52841a2258 bsv: a compressed checkpoint decodes to exactly its header's size
A zlib or zstd checkpoint that decodes short of its encoded size is
refused, rather than restored with the rest of the state left as the
decode buffer held it.
2026-10-09 10:33:57 +02:00
Acts1631 d3b50802c2 cheevos: bound progress deserialization to supplied state data (#19692) 2026-10-09 10:28:21 +02:00
Anthony Schemel 250e2d1b48 save: write states, SRAM and disk index through a temporary file (#19629) 2026-10-09 10:16:32 +02:00
LibretroAdmin 15dbae245c qb: configure options grouped by area
config.params.sh lists the options under section headings - build,
frontend features, menu, networking, crypto and network filesystems,
video drivers, windowing contexts, shaders, audio, input, SDL, camera
and recording, system integration, codecs, archives - with each C89_
and CXX_ line beside its option, and ./configure --help prints the
headings. Descriptions read as what the toggle switches.

Option names and defaults are unchanged; configure writes the same
config.mk and config.h.
2026-10-09 10:02:33 +02:00
LibretroAdmin 6d25a03d1e video: bind the OSD font's video state whatever the build leaves out
The OSD font reaches the video state through a binding made at every
video init, so the statistics show in a build without video filters.

samples/gfx/statistics_on_screen runs the real binary with the smoke
core and statistics on, threaded and not, and looks for the text;
Linux-overlay-on-screen runs it against that workflow's build, which
has no video filters.
2026-10-09 10:02:23 +02:00
LibretroAdmin d7fb8d9409 slang: the HLSL emitter comes with any Direct3D 10/11/12 driver
HAVE_HLSL is the Direct3D 9 HLSL driver. The unity build carries
SPIRV-Cross's HLSL emitter for it and for the Direct3D 10, 11 and 12
drivers, whose slang passes are translated to HLSL, and slang_process()
asks SPIRV-Cross for HLSL whenever a driver wants it; a build without
the emitter is refused there, with the backend named in the error.

samples/gfx/slang_hlsl_backend runs the real slang_process() on a
stock pass against SPIRV-Cross built for a Direct3D 11 build without
HAVE_HLSL and for a build with no Direct3D, and checks the unity
build's switch for each Direct3D configuration.
2026-10-09 10:02:16 +02:00
206144523a Network RetroPad: refuse a test-file button past bit 31 (#19759)
* Network RetroPad: refuse a test-file button past bit 31

expected_button comes from the .ratst file and may be up to 999 on
the pad path, and 1 << expected_button was shifted past the width of
int. A button past input_state's 32 bits now matches nothing.

Tests: headless, null drivers, with a .ratst naming button 40 and the
core built with -fsanitize=shift-exponent. Before: "shift exponent 40
is too large for 32-bit type 'int'" at test step 0. After: clean.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Network RetroPad: the input state's bits are unsigned shifts

The right stick's last direction and the 32nd button drawn are bit 31,
which a shift of a plain 1 cannot reach. The harness runs the core with
undefined behaviour fatal, a right stick held fully down, and a test
file naming a button past bit 31.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-09 10:04:30 +02:00
ab0732b895 ps2: check the font texture allocations in ps2_font_init (#19758)
* ps2: check the font texture allocations in ps2_font_init

ps2_font_init wrote through the texture calloc and the tex8 and
clut32 mallocs without checking them. Each failure now frees what
was made and returns NULL. ps2_font_free is not reused for that: it
reads font->texture before testing it.

Tests: none run, and not compiled: no PS2 toolchain here.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* ps2: a font leaves the texture manager, and its own memory, when freed

The texture manager finds a resident texture by its address, so a
font texture freed while still registered lends its VRAM block to the
next texture allocated there, whatever that one's size. The font
struct itself is freed with the rest, and a failed init frees through
the same path.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-09 10:04:14 +02:00
Anthony SchemelandClaude Opus 5.5 3af2e733bc vita_pib: start the shader log empty in logShaccCg (#19761)
With no diagnostics the loop never writes the log, and strlen then
read the caller's uninitialised stack buffer. Start it empty.

Tests: none run, and not compiled: no Vita SDK here.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 10:03:58 +02:00
Anthony SchemelandClaude Opus 5.5 a6a9930df1 save: zero the recovery file's time before rtime_localtime (#19760)
rtime_localtime returns its result pointer even when localtime()
fails, leaving the struct tm unwritten, and strftime then read it to
name the emergency save. Zero it first.

Tests: built; not reproduced, since a localtime failure on time()'s
own value cannot be forced.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 10:03:45 +02:00
Anthony SchemelandClaude Opus 5.5 a2b306d785 ctr: check the image before sizing it in ctr_load_texture (#19757)
ctr_load_texture read image->width and image->height to size the
upload before its own !image test, so a NULL image was dereferenced
before the guard meant for it. Size it after the guard.

Tests: none run, and not compiled: no 3DS toolchain here.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 10:03:29 +02:00
Roman Vasilyev cfbc895b78 Add a separate NFS cloud sync subdirectory (#19722) 2026-10-09 09:18:05 +02:00
libretroadmin 0d7b551d37 audio: build the integer front pair only behind its hint
The int16 resampler mirroring the float one is built at init only when
Resample to Fixed Integer is on, which is the one thing that lets the
s16 path run; the hint now reinits audio, as the HQ setting beside it
does, so the pair always matches it. With the hint off - the default -
init no longer builds integer tables no path reads: 2 MiB and 25 ms at
Highest, 3 MiB and 40 ms with HQ, on every audio init and reinit.

pipeline_format checks through real init that the pair follows the hint
and that a batch reaches the device either way, and now also runs under
ASan and UBSan in CI; hq_settings_test pins the hint's reinit.
2026-10-09 09:11:38 +02:00
Roman Vasilyev 1670ecd089 Fix duplicate NFS subdirectory in URL resolution (#19721) 2026-10-09 09:11:15 +02:00
Rob Loach 767c6f89c2 net: Add Basic Auth support to HTTP requests (#19717) 2026-10-09 08:57:26 +02:00
LibretroAdmin 1678ca5ab5 vulkan: park released textures on mpsc_stack; build on every atomic backend
The deferred-texture list is an mpsc_stack, which carries the
RETRO_ATOMIC_HAS_PTR gate and the volatile fallback, so the driver
builds on every retro_atomic backend. Helpers whose only callers sit
under HAVE_OVERLAY or HAVE_SHADERPIPELINE are gated with them, and
overlay_behind_menu is declared only with HAVE_OVERLAY.

The gfx atomic-backend portability step compiles vulkan.c on each
backend, from the Khronos headers in gfx/include.
2026-10-09 08:53:11 +02:00
LibretroAdmin 5221eea01f cloudsync: a download lands only whole, and never under a running core
A fetch is written beside its file and moved into place once complete,
unless a core has taken the file since the fetch began. A transfer that
fails part-way leaves the local file as it was.
2026-10-09 08:41:55 +02:00
LibretroAdmin 70ba39a934 net: a peer that goes silent ends the transfer
net_http fails a transfer that has moved nothing for 60 s of the time
it is driven. A TLS read - in the handshake, or the rest of a record -
waits at most SSL_SOCKET_IO_TIMEOUT_MS for the server's next bytes, on
the built-in client, mbedTLS and BearSSL alike.
2026-10-09 08:34:05 +02:00
libretroadmin ce804d6a45 sinc: extra channels share the front pair's tables
retro_resampler_t and retro_resampler_int16_t gain an optional sibling
operation: a new instance with a fresh stream of its own over another
instance's tables, freed like any other. Both sinc drivers keep the
immutable tables - the kernel's and the decimation stages' - in a block
of their own, reference-counted and freed with the last instance; the
rings, the stages' rings, the shadow and the blocks stay per instance.

The discrete extra channels are made as siblings of the front pair in
whichever domain they run, so a 7.1 HQ layout carries one 3 MiB table
per domain instead of one per pair; a sibling costs 72 KiB there, 50 KiB
at Highest. Backends without a sibling are made fresh as before.

samples/audio/sinc_sibling checks a sibling streams bit-identically to a
fresh instance whatever its parent does, through fast-forward stage
changes and a reset, in either free order, and allocates only its own
state; it runs natively, under MinGW and on the ARM kernels.
2026-10-09 08:16:26 +02:00
LibretroAdmin 4cd1e566a5 samples/gfx/gl3_shader_status: build against the tree's GL headers
<GL/gl.h> comes from gfx/include/MESA, so the test builds and runs
on a runner with no GL development package installed.
2026-10-09 08:13:20 +02:00
LibretroAdmin b21bf85367 vulkan: make built_hdr_output a retro_atomic_int_t
It is read and written through the retro_atomic int ops, which on the
C11 backend take an _Atomic object.
2026-10-09 08:13:18 +02:00
YaandxNITROx 84863c3a41 CLOUDSYNC: wait before loading CLI content (#19502)
Co-authored-by: xNITROx <[email protected]>
2026-10-09 08:16:11 +02:00
Rob Loach 9312a11000 cli: Add a --cloudsync command to sync the Cloud from the CLI (#19504) 2026-10-09 08:00:52 +02:00
473df1660d Prevent the microphone from automatically turning on when closing the menu (#19659)
* Prevent the microphone from automatically turning on

* alsa: a recovered microphone restarts only if the frontend has it on

A signal in the capture wait leaves a never-started stream prepared,
and a suspend the device cannot resume prepares it; the worker waits
on a microphone whether or not the core has it on, so neither may
start it.

---------

Co-authored-by: Rob Loach <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-09 07:43:53 +02:00
b4109477cb bsv: refuse a short replay copy; store the checkpoint config little-endian (#19714)
* bsv: refuse a short replay copy; store the checkpoint config little-endian

replay_get_serialized_data logged a short read of the replay into a
savestate and still returned true, so the savestate kept a replay block
whose tail was whatever the buffer held. It now returns false, as it
already does for a frame counter too large to store. Both callers in
task_save.c handle false by leaving the replay block out, so the
savestate is still written, without its replay.

The header's checkpoint-config word was written and read in native byte
order, while every other header field goes through swap_if_big32. It
now does too. On a little-endian machine the swap is a no-op, so files
from those builds are unchanged; a big-endian build now writes the same
bytes as everyone else.

Tests (samples/tasks/bsv_replay_init): serialize_short places a handle
past the end of a file shorter than its position. It fails without the
fix (128 / 256 bytes copied, copy accepted) and passes with it, in the
plain, ASan, ASan + STATESTREAM=1 and valgrind runs of the sweep.
config_read_order and config_write_order check the word's byte layout;
they pass with or without the fix on little-endian and were not run on
a big-endian machine. header_sizes now writes its fixture word through
swap_if_big32. bsv_replay and bsv_statestream pass; RetroArch builds;
both changed files compile with -std=c89.

write-test: route 1, inline; red run proved for serialize_short only,
the byte-order lanes are unproven here (no big-endian toolchain); no
mutation probe beyond the red run.
write-code: gate make -j4 retroarch rc 0; bsv_replay_init sweep clean;
bsv_replay and bsv_statestream pass; -std=c89 syntax check; diff read.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* bsv: the short replay copy's log passes its counts as ints

The message formats both with %d; read_amt is 64-bit, which on a
32-bit target shifts every argument after it.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-09 07:15:35 +02:00
LibretroAdmin 47ec8bab05 input: WinUSB HID driver for Windows
Windows' HID driver will not pass a DualShock 3 the feature request
that starts it, so on its own the pad sends nothing. Bound to Windows'
own WinUSB driver it is the program's: winusb_hid finds pads bound to
WinUSB whose ids name a handled pad (for now connect_ps3), opens the
interface the binding gave them, and the handler starts the pad with
HID class requests on the control pipe - SET_REPORT and GET_REPORT -
sets its LEDs and reads its pressures, as on Linux. The address the pad
connects to over Bluetooth is read and set the same way, so Input
Information has the pad's line and "Pair with This Computer" on Windows
too.

winusb.dll is loaded when the driver starts, so nothing new is linked;
cfgmgr32's interface notification, from Windows 8, has a pad bound
while it runs looked for. Each pad has a reader thread waiting on its
interrupt pipe, which hands the newest report to the poll through three
buffers swapped with an atomic exchange: no lock, and no call into the
system at the poll. Stopping aborts the pipe and joins the reader.

configure enables it on Windows with threads, which builds the hid
joypad driver; the default joypad driver is unchanged. The Windows
syntax check now builds with HAVE_HID and HAVE_WINUSB, as that build
does, and gives the HID files HAVE_HID on its C89 pass.

samples/input/winusb_hid runs it under Wine with the real pad handlers
and a stand-in for winusb.dll.
2026-10-09 07:06:37 +02:00
Anthony SchemelandClaude Opus 5.5 38c7b6fb06 bsv: free the replay buffers a failed read leaves behind (#19713)
bsv_movie_reset_playback leaked the state buffer of a version 0/1
replay whose initial state is shorter than the header says.

bsv_movie_read_deduped_state freed the rmsgpack item on some refusals
and not others, so a statestream checkpoint carrying a string or
array where a token, an index, a superblock or the sequence belongs
leaked it. The item now starts as RDT_NULL, is reset to RDT_NULL when
a block is handed to the index, and is freed once at the exit label;
rmsgpack_dom_value_free leaves it RDT_NULL, so the existing frees stay
harmless.

Tests: bsv_replay_init gains v1_short_state and bsv_statestream gains
wrong_type. Under ASan both leak before the fix (64 bytes from
bsv_movie_reset_playback; 7 allocations, one per position) and pass
after it, with every other lane still passing, in the plain and
STATESTREAM=1 builds of bsv_replay_init. RetroArch builds; bsvmovie.c
also compiles with -std=c89.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 07:07:47 +02:00
libretroadmin b5ca739cb4 sinc: decimate ahead of the kernel when fast-forward drops the live ratio
The sinc tables are built for the nominal ratio, and a live ratio below
it - Fast-Forward Audio Speedup, down to AUDIO_MIN_RATIO - leaves the
cutoff above the output Nyquist, so everything between folds back at
full level. Both drivers now halve the source rate as many times as the
live ratio needs before the kernel runs. Each stage is the preset's own
table for a ratio of 0.45, at the one phase an exact halving visits, run
through the kernels and SIMD dispatch already there, so rejection is
what a table built for the live ratio would give, at every preset.

The first stage joins below 0.97 of the design ratio, beyond rate
control's reach, and leaves inside its band; deeper stages leave well
clear of where they join, keeping 2x, 4x and 8x mid-band against
measured speed jitter. A change renders the old path alongside until
the new one's rings are full, then crossfades, so engaging and
releasing stay as smooth as the plain pitch change. Stage state is
allocated at init with the tables; processing allocates nothing. The
integer driver takes its decisions only when the ratio's bit pattern
changes, as it does for the time step.

samples/audio/sinc_fastforward checks rejection against natively built
tables, the untouched nominal path, the frontend's output bound,
hysteresis, transitions and reset, for both drivers, natively, under
MinGW and on the ARM kernels under qemu.
2026-10-09 06:54:31 +02:00
libretroadmin 73edc33d55 sinc: NEON assembly for the Kaiser layout
process_sinc_neon_kaiser_asm sits beside the Lanczos routine in
sinc_resampler_neon.S. It reads the delta row that follows each
coefficient row and interpolates coeff + delta * frac in registers
before accumulating, so builds with HAVE_ARM_NEON_ASM_OPTIMIZATIONS run
assembly for every preset. The fraction is passed by pointer, which
keeps hard- and soft-float ABIs alike.

sinc_hq compares against NEON on ARM, and the sinc samples take
NEON_ASM=1 to link the assembly. The cross-arch job runs sinc_dispatch
and sinc_hq under qemu: aarch64 on the intrinsics, armv7 on the
assembly.
2026-10-09 06:54:27 +02:00
Acts1631andLibretroAdmin d237a8043d mp4: reject fragment samples outside the file (#19718)
* mp4: reject fragment samples outside the file

A crafted tfhd base offset can make the sample end wrap in
rmp4_parse_traf(). The sample then passes rmp4_read_packet()'s
matching check and hands a pointer outside the file to a decoder,
causing an out-of-bounds read or crash.

Validate each fragment sample range with subtraction before storing
it, and check stored ranges again before returning a packet.

* rmp4: a trun of default-size-zero samples is passed over at once

With no per-sample fields and no default size, a trun's samples add
nothing to the track, so their time is added in one step instead of
walking a count the file sets, up to 2^32-1.

libretro-common/samples/formats/mp4 builds fragmented MP4s byte by
byte: two samples through a trun data offset come back as their bytes,
a base-data-offset whose sample end wraps gives no packet outside the
file, and a trun declaring 2^32-1 empty samples opens at once. It also
builds with mingw.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-09 06:55:36 +02:00
LibretroAdmin 2f4e275fab input: this computer's Bluetooth address on Windows
Information > Input Information showed no Bluetooth line on Windows:
the address was only read on Linux.

It is now read through the Bluetooth API's BluetoothFindFirstRadio and
BluetoothGetRadioInfo, loaded when asked for from bthprops.cpl (XP SP2
on) or BluetoothApis.dll (8 on), so nothing new is linked and a Windows
without them still starts, with no line. The two structures are
declared here, checked for their size, as older SDKs have no
bluetoothapis.h. Not on Xbox or UWP. The adapter's name, which on
Windows is the computer's, is given more room in the menu.
2026-10-09 06:41:30 +02:00
ZariepandLibretroAdmin 9a01d0a3c4 encoding_deflate: fix clang/gcc builds for pre-SSE4.2 targets (#19756)
* encoding_deflate: fix clang/gcc builds for pre-SSE4.2 targets

The runtime dispatch CRC32 hash called _mm_crc32_u32 from a function marked target("sse4.2"). Clang gates that intrinsic on a separate crc32 feature that the attribute does not imply, so builds for baseline x86_64 (e.g. core2) failed to compile even though the function is never called on such CPUs. Emit the instruction with inline asm in the GCC/clang runtime path; selection is still gated by cpu_features_get() at stream creation. MSVC path unchanged.

* encoding_deflate: test the run-time CRC32C hash against the checksum

rdeflate_hash_test builds encoding_deflate.c for a CPU without SSE4.2
(-march=core2 on x86), so the match finder's hash is the instruction
chosen at run time, and checks it against CRC32C computed bit by bit
for each value of each key byte and 200000 random keys. The deflate
samples link rtime.c, which features_cpu.c needs on Windows; both
tests build with mingw.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-09 06:40:30 +02:00
LibretroAdmin b142dfd787 input: pair a DualShock 3 with this computer's Bluetooth from the menu
A DualShock 3 connects over Bluetooth to the address it was last given
over USB, in its feature report 0xF5. Joypad drivers can now read and
set that address for a pad that keeps one (get_bt_host/set_bt_host,
also on the HID driver interface): the udev driver through the hidraw
node it reads the pressures from, and hidraw_hid through the pad's own
node, both for a pad on USB.

This computer's Bluetooth address is read on Linux from the kernel's
HCI socket, the first adapter that is up, with no BlueZ library.

Information > Input Information lists the adapter with its address,
and under a controller that keeps one the address it connects to -
"This Computer" when it is this one's - with "Pair with This Computer"
to set it when it is not.

samples/input/hidraw_hid checks that the address is read and set as
0xF5 most significant byte first.
2026-10-09 06:23:32 +02:00
LibretroAdmin 536bf13c24 audio-suite-check: a suite runs as long under TSan as CI lets it
900 seconds, as tools/tsan-samples.sh allows: discrete_multichannel
takes about three minutes under TSan on two cores.
2026-10-09 06:12:01 +02:00
LibretroAdmin 8d7f5e669d drm_ctx: the mode dump is built with the modelines that use it
dump_mode() and mode_vrefresh() serve only the HAVE_MODELINE path, so a
build without CRT SwitchRes has no unused functions.
2026-10-09 06:11:58 +02:00
LibretroAdmin d222bad6d0 connect_ps3: the DualShock 3's pressures as axes 4 to 15
The input report carries how hard L2, R2 and the face, shoulder and
D-pad buttons are pressed; the handler kept the bytes and gave nothing
past the sticks. They are now axes 4 to 15, from 0 to 0x7fff, in the
order SDL gives them (L2, R2, cross, circle, square, triangle, L1, R1,
up, down, left, right), so the hid driver's profile binds them as the
SDL one does. hidraw_hid passes axes past the sticks to the handler.
2026-10-09 06:05:49 +02:00
LibretroAdmin 681fdac478 Build without warnings on DOS, Emscripten and without Wayland
What a build without threads, BSV movies, networking, Wayland or the
XMB and Ozone menus does not use is not built there: the pipe's stereo
widening and frame bounds, the fallback font load states, the GL
texture lend slot, the softfilter core topology, the command reply
event, the History playlist settings, the netplay row's local and the
DRM context's Wayland device label. DJGPP's size_t is unsigned long,
so PRI_SIZET is "lu" there, and the SHA-1 digest words are printed as
unsigned.
2026-10-09 06:04:32 +02:00
LibretroAdmin d3dcb03fde threaded_video: a lane for gl and glcore init with no memory
The harness links calloc through --wrap (not on Apple's linker, which
has none) so a lane can fail the next call. Before the frontend
starts, gl and glcore init are each run with their struct's
allocation failing and must return no driver.
2026-10-09 06:04:28 +02:00
LibretroAdmin eb615cc2f8 ui_win32_companion: check the thumbnail slot table; test Add Files
The icon grid's slot table is checked: without it the grid has no
image list, the rows show without thumbnails, and the next reset
tries again.

The Win32 companion harness links realloc and calloc through --wrap to
fail one at a time: the slot table fails as a playlist lands with a
decodable boxart, and Add Files, answered from a timer inside the
modal dialog, has its path list's growth fail and still adds the eight
files gathered.
2026-10-09 06:04:25 +02:00
LibretroAdmin e77f1a3393 rsx: check the driver's other RSX allocations; regrow a texture
rsx_init(), rsx_font_init(), rsx_overlay_load() and
rsx_load_texture_internal() fail cleanly when an RSX allocation does,
freeing what they made, and rsx_frame() skips a frame whose texture
got no memory. A texture's buffer keeps its size, and a frame larger
than it gets a new buffer once the RSX is idle.

samples/gfx/rsx_alloc builds the driver on the host against PSL1GHT
stand-ins added to tools/platform_stubs/ps3 and fails each allocation
in turn; it also builds with mingw.
2026-10-09 06:04:20 +02:00
LibretroAdmin 3654b834ab thread_elevation: the test reads the Priority line as it is written
Main=, Video=, Audio= and Device= for each thread raised, then the plan.
2026-10-09 06:04:16 +02:00
LibretroAdmin 844a283621 retroarch, runloop: no unused locals or functions without threads
retroarch_task_queue_configure() declares threaded_enable only where it
is used, retroarch_main_init_core() reads the movie handle where the
cheats want it, and runloop_idle_wait() is built only where a frame
waits with it (not Emscripten's deferred main loop).
2026-10-09 06:04:13 +02:00
LibretroAdmin a0bc0b4069 vulkan: get_flags() reads what HDR output was built, atomically
The main thread asks the driver's flags under threaded video while the
video thread changes the context's; whether the end-of-frame readback
is the game's only now comes from an int the build notes with a
release store.
2026-10-09 06:04:09 +02:00
LibretroAdmin bb876d4d75 video_driver: recording_dump_frame() is handed perfcnt_enable
Its caller reads it, so the dump reaches no runloop state of its own
(tools/video_thread_state_check.py).
2026-10-09 06:04:06 +02:00
LibretroAdmin 4e1105171e record: the 32-bit readback asks whether the wrapper is up
video_driver_thread_wrapper_active() answers false on a build without
threads, where video_driver_state_t has no such field: DOS and
Emscripten build again.
2026-10-09 06:04:02 +02:00
LibretroAdmin fbe32fdf8f udev: read a DualShock 3's button pressures from its hidraw node
The kernel's driver gives a DualShock 3's evdev node its buttons and
sticks but not how hard each button is pressed. The pad's hidraw node,
under the same HID device, has the whole input report.

When the udev joypad driver adds a DualShock 3 it opens that node, read
only and without blocking, and at each poll takes the pressures of
cross, circle, square, triangle, L1, R1 and the D-pad from the newest
input report waiting into ten axes after the pad's own - 6 to 15, as
SDL gives them. Buttons and sticks still come from evdev, so the pad is
one pad. If the node cannot be read the pad works as before and the
log says why; if it goes, it is closed and the pressures rest at 0.

samples/input/udev_ds3_pressure runs the real driver with a socket for
the hidraw node.
2026-10-09 06:01:37 +02:00
LibretroAdminandClaude Opus 5.5 3f5530ba20 input: hidraw HID driver for Linux
The hid joypad driver had no backend on Linux but libusb, which takes a
pad from its kernel driver and was off unless --enable-hid. A DualShock
3 through it was not started either: connect_ps3 needs set_report and
get_report, which libusb_hid does not have.

hidraw_hid reaches pads through their /dev/hidraw nodes, beside the
kernel's driver. A node is opened only when its sysfs ids name a pad
whose handler frames reports as hidraw does (for now connect_ps3); its
reports are read without blocking at each poll, on the polling thread,
the newest being the pad's state, so there is no thread and no lock.
Features go through HIDIOCSFEATURE/HIDIOCGFEATURE and output reports
are written, so connect_ps3 starts the pad (0xF4, 0xF2, 0xF5) and sets
its LEDs. udev, where present, brings pads plugged in later; a pad
whose read fails is disconnected.

configure enables it on Linux when <linux/hidraw.h> is there, which
builds the hid joypad driver; libusb stays as --enable-hid leaves it.
The default joypad driver is unchanged.

samples/input/hidraw_hid runs it with the real pad handlers against
stand-ins for /dev and /sys.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BmYB6tAZ7Yo7XFMMSkYbwa
2026-10-09 05:29:13 +02:00
LibretroAdminandClaude Opus 5.5 7849023344 input: read an analog button's pad button from the frame's copy
An analog button with no axis behind it, or with its axis at rest, is
answered by the pad button bound to it. Where the read goes through
the frame's copy of the pad that is a bit of the copy, so it is taken
there instead of through the driver's button callback.

Twelve analog-button reads a frame on one port, as LRPS2 makes them,
with only the triggers on axes: 6,646 instructions a frame before,
6,398 after (callgrind, 300 frames, frontend overhead harness).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BmYB6tAZ7Yo7XFMMSkYbwa
2026-10-09 05:29:09 +02:00
libretroadmin cd26ec3e47 sinc: select the widest compiled kernel the SIMD mask allows
Each arm is now taken only when its kernel is compiled in and the mask
allows it, with wider arms overriding narrower ones. An SSE-only build
on an AVX-capable CPU therefore runs the SSE kernel for Higher, Highest
and HQ instead of the scalar one.

Builds with HAVE_ARM_NEON_ASM_OPTIMIZATIONS also compile the intrinsics
Kaiser kernel, since the external assembly covers only the Lanczos
layout; Kaiser presets there run NEON instead of scalar.

samples/audio/sinc_dispatch checks the selected kernel for every
quality, HQ shape and mask, in the SSE and AVX lanes.
2026-10-09 04:57:48 +02:00
25ed47e118 input_keymaps: check the keysym lookup table allocation (#19748)
* input_keymaps: check the keysym lookup table allocation

input_keymaps_init_keyboard_lut wrote into the calloc'd lookup table
without checking it. On failure, leave the table empty; the lookup
already falls back to its slow path when the table is NULL.

Tests: none run; no harness links input_keymaps.c. RetroArch builds with the change and the file compiles without warnings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* input_keymaps: test the keysym reverse table without memory

samples/input/keymaps_lut compiles input/input_keymaps.c into the test
with calloc() routed through it and checks that every mapped code
translates the same with the reverse table, with its allocation
failed, and with a map too wide for one. It also builds with mingw.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-09 04:56:00 +02:00
9572434f30 shader_vulkan: fail chain creation when its passes cannot be allocated (#19745)
* shader_vulkan: fail chain creation when its passes cannot be allocated

slang_chain_set_num_passes gave up silently when an allocation failed,
so slang_chain_new returned a chain with no passes, or fewer than
asked for. slang_chain_set_shader then indexed chain->passes without a
check. set_num_passes now reports failure, and slang_chain_new frees
the chain and returns NULL, which every caller already checks. A
request for zero passes still succeeds.

Tests: none run; no harness links shader_vulkan.c. RetroArch builds with the change and the file compiles without warnings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* shader_vulkan: a chain needs a disposal queue for each sync index

slang_chain_new() also fails, and slang_chain_update_swapchain_info()
returns false, when the deferred-disposal queues cannot be sized for
every swapchain image: a frame disposes into the queue of its index.

samples/gfx/vulkan_chain_alloc compiles shader_vulkan.c into the test
with Vulkan mocked through the symbol wrapper and fails each
allocation of chain creation in turn; it also builds with mingw.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-09 04:55:48 +02:00
19ad9e6d70 shader_gl3: fail chain creation when its passes cannot be allocated (#19744)
* shader_gl3: fail chain creation when its passes cannot be allocated

gl3_chain_set_num_passes gave up silently when an allocation failed,
so gl3_chain_new returned a chain with no passes, or fewer than asked
for. gl3_chain_set_shader then indexed chain->passes without a check.
set_num_passes now reports failure, and gl3_chain_new frees the chain
and returns NULL, which every caller already checks.

Tests: none run; no harness links shader_gl3.c. RetroArch builds with the change and the file compiles without warnings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* gl3_shader_status: test filter chain creation without memory

gl3_chain_alloc_test compiles shader_gl3.c into itself with calloc()
routed through the test and fails each allocation of gl3_chain_new()
in turn: it gives a chain with every pass asked for, or none. The
SPIRV-Cross stand-ins move to spvc_stubs.c, shared by both tests.
Both build with mingw.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-09 04:55:37 +02:00
a5184d9ad4 video_driver: realloc_checked reports a failed first allocation (#19746)
* video_driver: realloc_checked reports a failed first allocation

When the pointer was NULL and malloc failed, realloc_checked compared
NULL with NULL and returned true. video_coord_array_append then copied
vertices into the NULL stream. Return false when the allocation fails.

Tests: none run; no harness links video_driver.c. RetroArch builds with the change and the file compiles without warnings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* video_driver: free a coordinate array's streams whatever it counts

video_coord_array_free() frees every stream it holds, including those
an append grew before a later allocation of the same resize failed,
while nothing is yet counted as allocated.

samples/gfx/coord_array takes the coordinate array out of
video_driver.c and fails each allocation of an append onto an empty
array and of a growth in turn; it also builds with mingw.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-09 04:55:24 +02:00
Anthony SchemelandClaude Opus 5.5 5ab31dcd42 rsx: check rsxMemalign before using the texture buffer (#19751)
rsx_load_texture_data passed the rsxMemalign result to
rsxAddressToOffset and memcpy'd into it without a check. Return
early when the allocation fails.

Tests: none run, and not compiled: no PS3 toolchain here.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 04:55:12 +02:00
Anthony SchemelandClaude Opus 5.5 6815182675 gl2, gl3: check the driver struct before the context lookup (#19749)
gl2_init and gl3_init called get_context(gl) before checking that the
calloc of gl succeeded, and get_context writes gl->flags. Do the
lookup only once gl exists.

Tests: none run; no harness links gl2.c or gl3.c. RetroArch builds
with the change and both files compile without warnings.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 04:55:00 +02:00
Anthony SchemelandClaude Opus 5.5 80818d7971 ui_win32_companion: keep the paths array when realloc fails (#19750)
The Add Files dialog grew its paths array with paths = realloc(paths,
...), so a failed realloc lost the array and the next write went
through NULL. Grow through a temporary and, on failure, add the files
gathered so far. The single-file malloc is checked too.

Tests: none run. Not built for Windows here; the file passes
x86_64-w64-mingw32-gcc -fsyntax-only with no warnings.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-09 04:54:46 +02:00
github-actions fce35fc72b Fetch translations from Crowdin 2026-10-09 00:28:21 +00:00
LibretroAdmin a7363feb90 record_ffmpeg: the codec contexts are freed with avcodec_free_context
The audio and video contexts, which avcodec_alloc_context3() made, are
released with avcodec_free_context() on every FFmpeg the recorder
builds against, in place of an avcodec_close() and av_free() pair that
only FFmpeg 8 went without.
2026-10-08 21:08:06 +02:00
LibretroAdmin bf27741634 qb: FFmpeg's libraries are asked for at their 3.1 versions
The recorder and the built-in media player use the send/receive codec
API, which FFmpeg 3.1 brought (avcodec_send_frame is libavcodec 57.37),
so configure asks for that release's libraries rather than accepting a
3.0 that fails to compile them.
2026-10-08 21:08:06 +02:00
LibretroAdmin 5c8bfb5d3e audio: the microphone driver lookup declares before the alsathread alias
microphone_driver_find_driver() declared its settings pointer after the
HAVE_ALSA block that maps a configured "alsathread" to "alsa", which a
C89 compiler refuses. The alias now goes through that same pointer, set
up first.
2026-10-08 21:08:06 +02:00
LibretroAdmin d027a971e8 sdl3: a PS3 pad's pressure axes are known by being there, not by SDL's PS3 hint
A DualShock 3 under SDL's own driver for it (HIDAPI PS3) has ten axes
after the gamepad's six: how hard its face buttons, shoulders and
D-pad are pressed. When the SDL3 joypad driver sees them it reads them,
and the pad is given the "SDL3 PS3 Gamepad" profile, which binds each
of them beside its button - so a core that asks how hard a button is
pressed (LRPS2, libretro/ps2 issue 150) is told.

Whether the pad had them was asked of SDL_HINT_JOYSTICK_HIDAPI_PS3,
with a default of off. SDL's own default for the driver is on for
macOS and off elsewhere, so on macOS - with the hint unset, as it is
unless someone sets it - SDL gave the axes and the driver said there
were none: six axes read, the plain gamepad profile, no pressure. The
pad's axes are what says it now: a PS3 controller (0x054c/0x0268) with
more than six.

On Linux and Windows SDL's driver stays off unless the hint is set
(SDL_JOYSTICK_HIDAPI_PS3=1 in the environment); whether RetroArch
should turn it on there is left open - SDL keeps it off for reasons of
its own on each.

Compiled against SDL3's headers; not run: no PS3 pad here.

Section 24, step 0, of the input plan.
2026-10-08 20:33:26 +02:00
LibretroAdmin d5def50c35 recording: Record Game Only on gl1, and said once where a driver can't
gl1 reads a GPU recording's frame back right after the core's image is
drawn, before the menu, overlays and messages, when Record Game Only is
on and the output is not scRGB. Each video driver now says whether its
recording keeps to the core's image, as GFX_CTX_FLAGS_RECORD_GAME_ONLY:
gl, glcore and gl1 outside scRGB, vulkan outside HDR output, and sdl2
and sdl3, whose readback redraws the core's frame alone. A GPU
recording started with the setting on a driver without it says so once,
in the log and on screen.

threaded_video's record game only lane puts a message on screen over a
paused frame: on gl1 an ordinary read changes with it and a game-only
read does not; on sdl2 and sdl3 the game-only read does not. The
llvmpipe harness matrix gains gl1.
2026-10-08 20:32:20 +02:00
LibretroAdmin edf9c9b2ae input: the sticks' response curve; the shaping out of line, so a plain stick runs less
Analog Response Curve (input_analog_response_curve, 0.5 to 3.0 by 0.1,
1.0 by default) is how a stick's movement grows with its tilt: the
tilt past the deadzone, rescaled to the outer edge, to that power,
before the anti-deadzone lifts where it starts. Above 1 small tilts
move less, for finer control near the centre; below 1 they move more.
It is under Settings > Input with the deadzones, and with the rest of
them it is worked out into each port's values
(input_driver_deadzones_refresh()): a stick with only a curve set
takes the shaped path by a deadzone of a millionth.

The shaping is two halves now. input_stick_axis_scaled() stays inline
and, with no deadzone, does what it did; the shaped half -
deadzone, outer deadzone, curve, anti-deadzone - is
input_stick_axis_shaped(), out of line. With the curve's power in it,
the whole was too big to inline at all, and every stick read made four
calls: 156 instructions a frame more. Split, a stick with none of it
runs less than before - over 300 frames (X11), 3,436,392 instructions
with no controller and 4,097,054 with one become 3,432,136 and
4,092,809.

The frontend harness's lane on the deadzone numbers: a curve of 2
takes a tilt of 0.5 to 0.25 and of 0.5 a tilt of 0.25 to 0.5; past a
deadzone of 0.2 a curve of 2 takes 0.6 - halfway - to a quarter; full
tilt stays full. Every lane passes; the settings definitions and the
translation tables check.

The last step of the plan's section 23.
2026-10-08 20:26:46 +02:00
LibretroAdmin 6e3d224101 input: a stick drives the D-pad by how far it is tilted, as a bound axis presses its button
Analog read as digital went by two rules. An axis bound to a button,
and the OpenXR controllers' D-pad, compare the stick's raw tilt with
the axis threshold. The stick-drives-the-D-pad modes compared the stick
as shaped for the core - through the deadzone, and now the outer
deadzone, the anti-deadzone and the sensitivity. So the threshold
moved with them: with a deadzone of 0.3 a threshold of 0.5 came at a
tilt of 0.65, and an anti-deadzone pressed the D-pad at the deadzone's
very edge, whatever the threshold.

The D-pad modes read the stick as it is tilted now - the whole stick,
from one read, with no shaping - so the threshold is how far the stick
is tilted, everywhere ("How far an axis must be tilted", as the
setting says). The stick as the core reads it is shaped as before.

Over 300 frames (X11): 3,436,155 instructions with no controller and
4,096,817 with one on master, 3,436,392 and 4,097,054 with this - the
237 more, under one a frame, in input_driver_poll, which this does not
touch; the code before it moved.

The frontend harness's lane on the D-pad from the sticks: threshold
0.5 and deadzone 0.3 - a tilt of 0.55 presses right, where through the
deadzone it was 0.36 and did not; with an anti-deadzone of 0.6 too, a
tilt of 0.35 does not, where through it it was 0.63 and did. Every
lane passes.

The fifth step of the plan's section 23.
2026-10-08 20:01:57 +02:00
LibretroAdmin 37861a5d7b record_ffmpeg: a video queue within the address space, and no per-frame encoder log lines
The video ring is held under a byte ceiling, 256 MiB on 32-bit hosts and
1 GiB elsewhere: a queue deeper than that is shortened to fit, a ring
that still cannot be had is halved down to the minimum, and the size it
got is the one logged. The frontend's Debug log level asks libav* for
AV_LOG_VERBOSE, not AV_LOG_DEBUG, where encoders log every frame from
their threads.

ffmpeg_pipeline_test opens a recording of 16 MiB frames with a
128-frame queue and checks the queue it got is within the ceiling, and
that Debug logging stops at AV_LOG_VERBOSE.
2026-10-08 19:45:07 +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
LibretroAdmin 1a3c60f396 input: the sticks' outer deadzone and anti-deadzone
Two settings for the sticks, under Settings > Input beside the
deadzones, both 0 by default:

- Analog Outer Deadzone (input_analog_outer_deadzone, 0 to 0.5 by
  0.05): a stick tilted this close to its edge counts as tilted all the
  way - for worn sticks that stop short of full - and the way between
  the deadzone and that edge is spread over the whole range.
- Analog Anti-Deadzone (input_analog_anti_deadzone, 0 to 0.9 by
  0.05): movement past the deadzone starts at this share of full, so a
  core with a deadzone of its own (the N64 cores, say), which would
  otherwise come on top of this one, sees it at once.

The sticks are shaped by input_stick_axis_scaled(), the stick half of
what input_joypad_axis_scaled() did; the triggers keep that, with
neither of these. A tilt past the deadzone is rescaled to the way up
to the outer edge, capped at full, then lifted by the anti-deadzone,
its direction kept. A deadzone that reaches the outer edge leaves no
way between them, and past it is full.

Nothing on a frame where neither is set: the two are read only on the
shaped path, which a stick with no deadzone does not reach. Worked
out with each port's deadzones (input_driver_deadzones_refresh()), a
stick with only the outer or the anti one set is given a deadzone of a
millionth, to take it through the shaping. Over 300 frames (X11):
3,436,719 instructions with no controller and 4,098,599 with one
become 3,436,116 and 4,096,806.

The frontend harness's lane on the deadzone numbers: deadzone 0.2 and
outer 0.1 - a stick at 0.85 reads 0.65 / 0.7 of full and at 0.95
full; anti-deadzone 0.3 with them - at 0.5 it reads 0.3 + 0.375 * 0.7;
the anti-deadzone alone - just off centre starts at 0.3, at rest
nothing. Every lane passes; the settings definitions and the
translation tables check.

The fourth step of the plan's section 23.
2026-10-08 19:34:38 +02:00
LibretroAdmin b80931130c input: a controller can have deadzones of its own, from its profile; each port's are worked out ahead of its reads
One stick deadzone and one trigger deadzone served every controller.
A controller's profile can now have its own, under the same keys as
the settings - input_analog_deadzone, input_analog_trigger_deadzone -
and a controller with them reads by them; one without reads by the
settings. They are taken when the controller connects and dropped when
it goes, and Save Controller Profile writes them where it has them.

A read does not look for them. Each user port's two deadzones are
worked out ahead of time - its controller's own, else the settings' -
by input_driver_deadzones_refresh(), when one of what they come from
changes:
- a controller connecting or going (its deadzones set or dropped);
- the autodetect task giving the ports other controllers;
- a write from the menu - a deadzone setting, a port's controller
  (and Start on it, which puts the port back to its own);
- a configuration loaded, an override loaded or put back.
Every stick and trigger read then takes its port's value where it
took the setting - the core's reads, the D-pad from the sticks, the
mapper's, the menu's - and tests nothing.

Looking a controller's deadzone up in each read cost 24 instructions
a frame; looked up only behind a test of whether any controller had
one, five - the test itself, every stick read. Worked out ahead, the
reads cost less than before: over 300 frames (X11), 3,443,045
instructions with no controller and 4,104,951 with one become
3,436,719 and 4,098,599, the reads indexing a port's value in place of
loading the setting through the settings pointer.

The frontend harness's lane on the deadzone numbers: a controller's
own deadzones (0 for its stick, 0.5 for its triggers) over the
settings' 0.2 - port 1 takes them, a stick at 0.5 reads 0.5 and a
trigger at 0.6 reads 0.2 of full; read back as set; and dropped, the
settings' apply again. The harness writes the settings directly where
it sets up a lane, and works the ports' values out after, as the
program's own writes do. Every lane passes.

Not run: a profile with the keys read by the autodetect task, and a
configuration file's deadzone taken at load, end to end.

The third step of the plan's section 23.
2026-10-08 19:15:57 +02:00
2c7d46deef FFmpeg core: no hang on video gaps, decoder detection, more formats (#19753)
* ffmpeg core: don't hang on gaps in the video track

Playing a file whose video track has a gap of more than about two
seconds (frames dropped while recording, or any variable frame rate
source) froze RetroArch at that point. The main thread waits for the
next frame, and the decode thread only decodes a video packet once
audio has caught up to it, so across the gap it decodes audio that
nobody takes until the audio queue is full, then blocks too.

Wait for the next frame as before, but give up once the decoder has a
second or more of audio buffered ahead, which only happens across a
gap: hold the frame on screen, keep playing audio, and pick the next
frame up when the decoder reaches it. The interpolation factor is
clamped, since a held frame would push it outside [0, 1].

ffmpeg_sweep_test plays a clip with a 4 s hole in its video track and
checks audio and video both carry on past it.

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

* ffmpeg core: detect working decoders instead of taking the first

avcodec_find_decoder() returns one decoder per codec id, whichever the
build registered first, so which decoder played a file depended on
registration order and on what that platform's FFmpeg includes. A
platform's hardware video decoder (h264_nvv4l2, h264_v4l2m2m,
h264_rkmpp, h264_cuvid, h264_mediacodec, ...) was only used if the
build moved it to the front (the L4T FFmpeg patches do, to make every
application use nvv4l2), and a default decoder that can't work there
failed the file even when another decoder for the codec was built in.

For every audio, video and subtitle stream, collect the
non-experimental decoders for its codec and try them in order:
  1. video only: platform hardware decoders (AV_CODEC_CAP_HARDWARE),
     unless disabled by the new core option 'Use platform hardware
     decoders' (ffmpeg_hw_wrappers, default enabled);
  2. the default decoder (still libdav1d first for AV1), with the
     existing hwaccel set-up for video;
  3. any other software decoder.
For audio and video, open each and decode from the start until a
frame comes out, then rewind; keep the first whose frame this core can
use (video: a format swscale accepts, or the hwaccel format it set up,
so a wrapper that only returns DRM-PRIME surfaces is skipped; audio: a
sample format swresample converts). Subtitles, inputs that can't be
rewound, and codecs with a single decoder take the first decoder that
opens. The hwaccel configuration now comes from the decoder actually
being opened. The chosen decoder is logged, and a missing one names
the codec.

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

* ffmpeg core: claim more media formats, but only those the build can open

The player claimed a fixed list of 28 extensions, and the frontend's
media type table only routed those to it, so common files (.ogv, .mka,
.opus, .aac, .ac3, .aiff, .wv, .ape, .mts, ...) didn't open with the
built-in player at all.

- The core keeps its list and adds 31 more extensions, each only if
  this build has the demuxer and, where the extension implies a codec
  (.ogv Theora, .ape APE, .wv WavPack, .ac3 AC-3, ...), the decoder.
  RetroArch's platforms ship very different FFmpeg builds, so
  valid_extensions is built once, at runtime.
- libretro_ffmpeg_ffmpeg_core_supports_extension() exposes the same
  check; msg_hash.c lists the extra extensions as MOVIE_OTHER /
  MUSIC_OTHER (after everything else, so existing claims win) and
  path_is_media_type() only routes them to the player when it passes.
  MPV builds route them unconditionally.

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

* ffmpeg core: an unload frees its codec contexts and a hardware device has one owner

The codec contexts are freed at unload whatever the FFmpeg version, not
only closed, and a hardware device is handed to the codec context that
uses it rather than referenced from it, so freeing that context closes
the device - every decoder the probing opens and drops included.

ffmpeg_sweep_test runs its ASan pass with leak detection: every load and
unload in it must give back what it took.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-08 18:19:47 +02:00
LibretroAdmin 7c9dc21533 input: an analog trigger has a deadzone of its own
Analog buttons - triggers, and pressure-sensitive face buttons - went
through the sticks' radial deadzone, with their own reading as the
magnitude: a stick deadzone of 0.2 took the first fifth of every
trigger's pull, and there was no way to have one without the other.

"Analog Trigger Deadzone" (input_analog_trigger_deadzone, 0 to 0.95
by 0.05, 0 by default) is the analog buttons' own, on their own
travel: past it the rest of the pull is rescaled to the whole range.
It is under Settings > Input beside the stick deadzone, which now
applies to the sticks only. Someone who had set a stick deadzone gets
triggers with none until they set this one too.

The read itself:
- one read of the axis, with or without a deadzone - with one it was
  read twice, once for the magnitude;
- a trigger learned to rest at the far end (Full-Range Triggers) goes
  through the deadzone and the sensitivity too, from its pull; it
  skipped both.

Nothing on a frame: over 300 frames (X11), 3,443,045 instructions with
no controller and 4,104,951 with one, against 3,443,056 and 4,104,940.
(A first version that passed the full-range setting in as well cost
seven instructions a frame in the poll; the call has the shape it had,
the trigger deadzone in the stick deadzone's place.)

The frontend harness's lane on the deadzone numbers holds that with
only the stick deadzone set a trigger at 0.6 reads 0.6, and with the
trigger's own at 0.2 a trigger at 0.6, 0.1 and full reads half,
nothing and full. Every lane passes; the settings definitions and the
translation tables check.

The second step of the plan's section 23.
2026-10-08 18:14:46 +02:00
sonninnos e1035c9833 Compact video stats more 2026-10-08 19:05:51 +03:00
21b1255f9c vulkan: don't keep a buffer-chain node whose buffer was not mapped (#19747)
* vulkan: don't keep a buffer-chain node whose buffer was not mapped

vulkan_create_buffer returns a buffer with mapped == NULL when
creation, allocation or vkMapMemory fails. The buffer chain kept such
a node, and suballoc then handed out NULL plus an offset as the
range's data. Drop the node instead; vulkan_buffer_chain_alloc already
returns false when no node can be made.

Tests: none run; no harness links vulkan.c. RetroArch builds with the change and the file compiles without warnings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* vulkan: an HDR uniform buffer that could not be mapped is not written

The HDR pass writes its uniforms through the buffer's mapping, which is
NULL when vkMapMemory failed at init; the pass is skipped then, as it is
when no descriptor set can be had.

check-vulkan-mapfail runs the Vulkan lanes through the fake memory
layer with the first map of a 64 KiB buffer refused on each device -
the size of a buffer-chain block - and every lane must still pass. A
chain that kept the unmapped node wrote through NULL; the Vulkan
validation workflow runs it after the XRGB8888 pass.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-08 18:05:45 +02:00
LibretroAdmin 4ab034a695 input: past the deadzone a stick's tilt is rescaled in a straight line again
With a deadzone set, a stick's axis past it comes to the axis as a
share of the stick's tilt, times how far past the deadzone the tilt
is, rescaled to the rest of the way - every release has had it as

   val * MAX(1, 1 / mag) * MIN(1, (mag - deadzone) / (1 - deadzone))

When that was rewritten on master, the first factor came out the other
way round: 1 under a magnitude of 1, and 1 / mag over it. So past the
deadzone an axis was its own tilt times the rescaled tilt - half a
tilt with a deadzone of 0.2 read 0.19 of full where it is 0.375, the
diagonal and every other direction the same, and a trigger (an analog
button, through the same code on its own travel) at 0.6 of its pull
read 0.3 where it is half. Full tilt and the deadzone's edge were
right, which is why it did not show at once. Released versions, 1.22.2
the last, do not have it; the default deadzone is 0, which does not
go through this.

The frontend harness gains a lane on the numbers, with a deadzone of
0.2: the stick at half on one axis, on the diagonal, inside the
deadzone, at full, and up and to the left, each read as the formula
above gives it; and a trigger at 0.6, 0.1 and full. Run against the
code before this, it fails - 6143 where it is 12287, 9829 where it is
16383. Every lane passes.

Nothing on a frame: over 300 frames (X11), 3,443,056 instructions with
no controller and 4,104,940 with one, against 3,443,056 and 4,104,929.
2026-10-08 17:51:01 +02:00
Davey Hughes 38192e4cce video_thread: the headset calls reach the driver under threaded video (#19736)
The wrapper's tables left the headset refresh query and the three VR
calls empty. With Threaded Video on, the menu offered fallback rates,
the headset was never asked for its rate, and on the Quest a VR core
saw no headset, had its stereo map refused and got no poses.

The refresh and the views status are answered on the caller's thread:
the drivers read only what the headset thread publishes and what init
and free change. The stereo request and the poses run on the video
thread, as do the Quest context's sample, reference-space change and
stereo flag, after the frames already queued: the frame locates its
views in that space and is submitted with the poses it was drawn
with, so tracked content runs in step with the video thread.

Headset pacing stays off under threaded video, as before: the tick
wait is in the driver's frame, which does not hold the core there.
2026-10-08 17:50:23 +02:00
LibretroAdmin cd0e31dfee input: the buttons a stick presses as the D-pad are worked out once a frame, from one read of each stick
With a stick driving the D-pad, every read of a port's pad by the core
went through every stick the mode uses, axis by axis, each axis its
own reads of the pad - with a deadzone, six. A core that reads its pad
a button at a time did all of it again for each button.

The buttons the sticks press are worked out once a frame now and kept
with the mode they were worked out under (as a stick read for the core
is): input_port_dpad_bits(). Each stick in it is read whole, both axes
from one read of the pad, with input_joypad_analog_stick(), which reads
what input_joypad_analog_axis() reads axis for axis. The poll drops
what was kept, with the rest of the frame's reads.

Sixteen reads of a button, with the left stick driving the D-pad, asked
the pad for its axes 24 times; they ask 4 times now, and a second pass
in the frame none. Over 300 frames (X11): 3,445,236 instructions with
no controller and 4,106,553 with one become 3,443,056 and 4,104,929.

The frontend harness gains a lane: the left stick to the left and up,
driving the D-pad, read a button at a time - left and up are pressed,
the same on a second pass, the stick's axes asked for at most 4 times
for the sixteen and none for the second pass; the mask has them; and
after a poll the stick to the right is read afresh. Every lane passes.

The first step of the plan's section 23 (sticks, triggers and
deadzones).
2026-10-08 17:31:36 +02:00
libretroadmin 7a1f46f7a4 Small cleanups for dispserv_kms 2026-10-08 17:19:42 +02:00
Davey Hughes 011f81dbe0 kms: Scanline Sync on the DRM context, and tearing with vsync off (#19703)
* video: estimate the beam line from the time since line 0

For display servers that know when a frame's line 0 started and how
long a frame lasts, but cannot read the beam itself.

* kms: report the beam position and the mode's total lines

The line is worked out from the latest vblank timestamp and the mode's
timings, re-read on every call so the nominal pixel clock's drift never
exceeds a frame. A CRTC with VRR on is refused, as its blanking then
outlasts the mode.

The vblank query issues drmCrtcGetSequence()'s ioctl directly, as the
wrapper needs libdrm 2.4.89; a libdrm without the ioctl builds with the
beam unavailable.

The Scanline Sync menu entries now show wherever the display server can
read the beam, and while the setting is on, not only with D3DKMT.

* video: Scanline Sync takes the display's line count

The resolution table has no entry for many modes, so the count comes
from the display server where it knows the mode.

* kms: tear with async page flips when vsync is off

Every flip waited for vblank, so vsync off only dropped frames and
Scanline Sync could never place a tear. Fast-forward with vsync on still
drops frames without tearing, as Vulkan does with MAILBOX.

* samples: a live check for the KMS beam estimate

Flips black in blanking and white at an estimated line, with red ticks
where the flip was requested and green ticks where it had landed; with a
right estimate the edge sits between them, about as far below red at
every target. Run from a text console; no check target, since it needs
DRM master.
2026-10-08 17:16:10 +02:00
LibretroAdmin 39cce788cf input/osk: the Chinese pages - the index's missing key, the page triggers, a page as one string
On top of the Chinese keyboard pages (pull request 19735):

- The index page had 43 keys, the others 44. Copied into the grid by
  its size, it left the grid's last key as the page before had it - a
  character, or the next-page key - drawn and acting as that. It has
  44 now: a blank before the house key.
- i, u and v on the index have no page and did nothing when pressed.
  They are blank keys.
- The menu's page triggers (L and R) and the next-page key stepped one
  page at a time through every value up to the last, so with the 64
  Chinese values at the end, going round the keyboard's pages took
  64 more steps, and L from the first page landed on the last page of
  z. Both go through one call now, input_osk_step(): round the Latin,
  symbol and other languages' pages and the Chinese index, and not
  through the pages of characters, which step among themselves - the
  index before the first and after the last, as before the only way
  from one page of an initial to the next.
- A page of characters was 44 pointers to strings, 2,816 in all, each
  one a relocation where the program is loaded at an address of its
  own (Android, and every other position-independent build): fixed up
  at every start. Every character here is 3 bytes of UTF-8, so a page
  is one string of up to 30 now, and input_osk_chinese_grid() lays it
  out as the 4x11 grid - the digits and backspace on top, ten
  characters on each row below, return, the down arrow and the house
  at their ends - when the page is shown. The 63 identical cases that
  copied each page into the grid (380 lines) go. Built for x86-64 as
  position-independent code, the two files have 484 data relocations
  in place of 3,244, and are 24,136 bytes smaller.
- The table's header named the machine path its text was counted
  from; it says what the table is instead.

The script that counted the characters was not in the pull request.
tools/osk_chinese_pages_check.py holds the table to what the code
needs of it: a page for every value of the enum, in its order, each
of at most 30 characters of 3 bytes, none on two pages; an index of
44 keys with an initial's key where it has pages and a blank where
it has none.

The frontend harness gains a lane: stepping on goes round every page
once, by the index and through no page of characters; back from the
first page is the index; the pages of characters step among
themselves, the index at both ends; a page is laid out as the grid,
blank past its last character; and the index laid out after a full
page keeps nothing of it.

Not run on a device: the keyboard has not been seen drawn, as it was
not for the pull request.
2026-10-08 17:08:09 +02:00
ethan 4f75fd9107 input/osk: add Chinese keyboard pages (pinyin initial index)
The on-screen keyboard behind the menu's text entries has pages for
Japanese kana and Korean since the extended-IME work, but nothing for
Chinese - which needs a different shape, since 3500+ common characters
cannot fit a 4x11 grid the way 24 jamo or 92 kana can.

Rather than a pinyin IME, this adds a two-level layout:

  * an index page holding a-z, and
  * 63 character pages, one per pinyin initial (a, b1-b3, c1-c3, ...),
    each holding 30 of the most common characters for that initial

The character set is not guesswork: it is counted from the game titles
that appear in playlists (8171 titles, 1515 distinct hanzi, 21521
occurrences), ordered by frequency, so the first page of each initial
holds the characters people look for most.

Tapping an initial on the index jumps to that initial's first page,
tapping a character appends it to the line, and the house key returns
to the index. Characters are whole UTF-8 codepoints, so unlike Korean
no composition step is needed.

Gated behind HAVE_LANGEXTRA like the other extra pages, so builds
without it are unaffected.

Verified: compiles, and all 1515 characters are present in the
resulting library by byte comparison. No on-device run - the test
device's ROM blocks adb input injection.
2026-10-08 17:08:09 +02:00
a1bba98b1d translation: check a BMP reply against its size; scale by the image (#19743)
* translation: check a BMP reply against its size; scale by the image

On the direct-to-framebuffer path (no widget overlay), the BMP branch
of handle_translation_response:
- read the 54-byte header before checking the reply held 54 bytes;
- took width and height from the server and copied width x height x 3
  bytes from the reply without checking the reply held them;
- left a failed malloc of the image to the scaler, which then read
  through NULL.

Check the reply length first, refuse a zero or oversized image, and
give up if the malloc fails.

Two more changes are needed for a well-formed reply that does not
match the frame. The scaler's input stride was the frame width, not
the image width, so an image narrower than the frame was read past its
end. And the scaled output, which is frame-sized, was handed to
video_driver_frame with the image's width and height, so an image
larger than the frame read past the output buffer. Step by the image
width and pass the frame's size.

Tests: none run; no harness links task_translation.c and it needs a
translation server reply. RetroArch builds with the change and the
file compiles without warnings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* translation: the reply image is read as the format lays it out, and the scaler is released

A BMP reply is read at its own pixel offset with rows padded to four
bytes, bottom-up or top-down by the sign of its height, and anything but
24 bits per pixel or a side past 16384 is refused. A PNG reply that
does not decode, or a reply shorter than its format's header, draws
nothing. The RGB565 output steps by its row in bytes, a frame of no size
draws nothing, a scaler whose filter fails is not used, and the
scaler's buffers are freed with it.

samples/tasks/translation_reply runs replies through the real scaler
into a stand-in frame: same size, narrower, larger, padded rows,
top-down and RGB565 replies land with their rows in place at the
frame's size, and short, truncated, non-24-bit, widthless and
undecodable ones draw nothing, all leak-free under ASan.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-08 17:00:08 +02:00
5d6fbbee8f playlist: a pushed entry starts with no thumbnail-name flags (#19742)
* playlist: a pushed entry starts with no thumbnail-name flags

playlist_push_new_entry shifts the list down one and builds the new
entry in slot 0, resetting each field it owns. The runtime and
last-played setters are read-modify-write on attr, so the bits they do
not own, the thumbnail-name flags, kept whatever slot 0 held: the flags
of the entry that had been on top, or uninitialised heap on the first
push. Clear attr with the other fields.

Tests (samples/playlist): new lane
lane_push_starts_without_thumbnail_flags sets every flag on the top
entry, pushes another, and checks the new entry has none while the old
one keeps its own. It fails on
upstream master ("inherited thumbnail flags 0xf") and passes with the
fix; all 19 lanes pass. RetroArch builds and playlist.c compiles
without warnings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* playlist: a pushed entry's packed words are written whole

The pushed entry's runtime, last-played and attribute words are stored
outright - the caller's slot and nothing else - with the runtime status
set on top, in place of a read-modify-write setter per field. No bit of
the entry slot 0 held before carries over.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-08 16:52:51 +02:00
8ea3ff99b4 menu: content info no longer dereferences a NULL content path (#19738)
* menu: content info no longer dereferences a NULL content path

menu_displaylist_parse_content_information builds the Path line with
`*content_path ? content_path : "N/A"`. content_path comes from the
playlist entry, which can have no path. The block above already guards
the contentless case with `!content_path || !*content_path`, but a
core that needs content still reaches the Path line with NULL. Test
for NULL before the first byte, as the label code does.

Tests: none run; no harness links menu_displaylist.c. RetroArch builds
with the change and the file compiles without warnings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* menu_playlist_nav: content information for a playlist entry with no path

A lane loads a playlist whose one entry has a label and a core but no
path, selects it and builds its Information screen through the real
menu_displaylist_ctl: the Path row reads Not Available.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-08 16:49:27 +02:00
0479d94b1e glcore: a failed program link with no info log is still a failure (#19739)
* glcore: a failed program link with no info log is still a failure

gl3_cross_compile_program deleted the program and failed only inside
the branch that read a non-empty info log. A driver that reports a
failed link with an empty log, or a failed malloc of the log, fell
through and the unlinked program was returned as success. Fail the
link whatever the log holds, as the SPIR-V path in the same file
already does.

Tests: none run; forcing a link failure needs a GL context. RetroArch
builds with the change and the file compiles without warnings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* glcore: a failed shader compile with no info log is still a failure

gl3_compile_shader returned the shader as compiled when the driver
reported a failed compile with an empty log, or the log's malloc
failed, the same way the program link did. It fails now whatever the
log holds. Its only caller is gl3_cross_compile_program, so it moves
next to it in shader_gl3.c as a static.

samples/gfx/gl3_shader_status builds a program through the real
shader_gl3.c against a mock driver that fails each stage's compile and
the link, with and without a log: every failure returns no program,
says so, and leaves no shader or program behind.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-08 16:43:31 +02:00
LibretroAdmin b0eb7219af menu: a combination's response time is Instant or Hold For, and Hold For opens a list of how long
The response time was one list of fixed choices - Instant, and Hold
for 0.5, 1, 2, 3 or 5 seconds ("Hold for 1 Seconds" among them).

It is two steps now:
- "Response Time": Instant, or Hold For.
- After Hold For, "Hold For": half a second to ten, by half
  seconds - "0.5 Seconds" ... "10.0 Seconds".
Each list has what the combination has now checked; Back out of
either and it keeps what it had. Like the one before, each list
opens at the next pass of the menu, after the one picked from is
taken down.

The strings: "Hold For" (the choice, and the second list's name with
a description), and "%s Seconds" for the times; Response Time's
description says that Hold For's time is chosen next.

Nothing on a frame: over 300 frames (X11), 3,445,236 instructions
with no controller, as before.

The frontend harness's lane on picking: Response Time is Instant and
Hold For; after Hold For the list of how long is up, with twenty
times from 0.5 to 10.0 seconds; 2.0 picked makes "l3+r3 held 2s :
menu_toggle"; and after Left and a macro, Back out of Response Time
keeps the two seconds. Every lane passes.
2026-10-08 16:39:24 +02:00
0e741ea810 glslang: free the scratch block on the include-cache hit returns (#19740)
* glslang: free the scratch block on the include-cache hit returns

glslang_read_shader_file_internal allocates its scratch block before
the include cache lookup. The two early returns on a cache hit
returned without freeing it, leaking one block per cached include.
Free it on both.

Tests: none run; no harness links glslang_util.c. RetroArch builds
with the change and the file compiles without warnings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* glslang: a cached include is copied before any scratch is allocated

A cache hit copies the include's lines and needs none of the scratch
block, so the cache is asked before the block is allocated rather than
the block being freed on each hit return.

samples/gfx/slang_include_cache expands a root that includes one leaf
three times, from sources served in memory, with and without a caller's
cache and for the pragmas-only read: each copy matches the first
expansion, the leaf is read from its source at most once, and the
unclaimed-samples job's ASan leak check fails a hit that keeps memory.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-08 16:31:10 +02:00
66cd07185c gfx_thumbnail: stop leaking the savestate thumbnail path (#19741)
* gfx_thumbnail: stop leaking the savestate thumbnail path

gfx_savestate_thumbnail_get_path, for a playlist entry, built the
state path in a heap block, then strdup'd it into state_name and freed
the block. The strdup was never freed, so each call leaked it, and a
failed strdup passed NULL to strlcpy. Point state_name into the block
instead and free the block on the function's exits.

Tests: none run; no harness links gfx_thumbnail.c. RetroArch builds
with the change and the file compiles without warnings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* gfx_thumbnail_preview: the savestate thumbnail path for a playlist entry leaves nothing behind

A lane builds the savestate thumbnail path for a playlist entry with no
core running, 64 times over, and checks it comes out of the savestate
folder under the entry's name. Under the CI's ASan leak check, a path
the function keeps fails the run.

The stub playlist hands out one entry, and settings_layout_uut.c, built
with the UUT's flags, lays the entry, the menu's selection and the
runloop's savestate folder into the stubs' blobs.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-08 16:27:10 +02:00
LibretroAdmin 7cee91e591 menu: a combination row's Left and Right in Ozone's footer, a response time after the action, and descriptions
On a combination's row Left chooses what it does and Right sets its
buttons, and nothing on the screen said so.

- Ozone's footer shows, while a combination's row is selected,
  "Choose Action" with the d-pad's left and "Set Buttons" with its
  right, beside "Reset to Defaults"; tapping either does what the
  button does. Only Ozone draws button legends; the other menus are
  as they were.
- After what a combination does is picked, a second list opens:
  "Response Time" - Instant, or Hold for 0.5, 1, 2, 3 or 5 seconds -
  which is how long the buttons must be held together first, the
  time an entry already had ("start held 2s : ..."). Back out of it
  and the time is as it was: Instant for a new one. It opens at the
  next pass of the menu, the list picked from being taken down after
  its handler returns.
- The combination and macro rows have descriptions now, saying what
  they are and what Right, Left, OK and Start do on them; and the two
  lists' descriptions say what they are for, the action's more fully
  than before.

The new words - the two legends, the list's name, "Instant", "Hold
for %s Seconds", the descriptions - are in the English strings, for
translating.

Nothing on a frame: over 300 frames (X11), 3,445,236 instructions
with no controller and 4,106,553 with one, as before.

The frontend harness's lane on picking goes on to the response time:
the list is up after the action is picked, and is Instant and five
times; two seconds picked makes "l3+r3 held 2s : menu_toggle"; and
after Left and a macro picked, Back out of it keeps the two seconds.
Every lane passes. Ozone is not in the Linux build here: its file
was compiled with it enabled and not run, so the footer has not been
seen.
2026-10-08 16:17:57 +02:00
a370be94d1 pipewire: don't dereference a NULL mic when opening the mic fails (#19737)
* pipewire: don't dereference a NULL mic when opening the mic fails

pwire_microphone_open_mic jumps to its error label when there is no
driver context or the calloc of the mic fails. mic is NULL on both
paths, and the error label called pwire_microphone_close_mic(mic->pw,
mic). Close only when mic was allocated.

Tests: none run; no harness reaches this path. RetroArch builds with
the change and pipewire.c compiles without warnings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* pipewire: a failed mic open does not destroy a stream it never made

pwire_microphone_close_mic() destroyed mic->stream unconditionally, and
a failed open reaches it before the stream exists when pw_properties_new
or pw_stream_new fails; pw_stream_destroy() dereferences its argument.
Destroy the stream only when there is one, as the playback close does.

pipewire_write_test opens a mic with no driver context and with one
whose stream cannot be made, and checks both fail without a NULL
destroy.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-08 16:16:55 +02:00
LibretroAdmin dfc52a8f7d libretro-common: rrar decodes a member into the caller's memory and says how far it has got
A compressed RAR member decodes from its start to its end, and for a
large one that takes a while with nothing to be had until it is done.

rrar_archive_extract_to() decodes into memory the caller has and, given
a watcher, tells it about every 64 KiB how many bytes from the member's
start are final, so that another thread can read them from the buffer
while the rest is decoded. The watcher can also stop the decode
(RRAR_ERROR_CANCELLED).

What is decoded is final unless a filter is still to be run over it,
and the filters were all run at the end. A filter is read before the
bytes it is for, so everything before the first filter not yet run is
final; and a filter is now run, in place and in the order they were
read, as soon as the decoder is so far past its block that nothing
still to be decoded can refer back into it - a dictionary's length,
which each entry now carries (window). Without a watcher they are all
run at the end, as before.

Decoding costs nothing more for it: the LZ loop's bound is the next
watch point where it was the member's size, which is what it still is
without a watcher. A real 176 MB member (RAR 4, best, 4953 filters, 4946
of them run before the end): 2.30 s without a watcher, 2.22 s with one
that checks every byte it is told about.

samples/formats/rrar: every member of every archive is also decoded
with a watcher that holds each count it is given to the member's bytes
at that moment, and one that stops it. Passes under ASan and UBSan.
2026-10-08 15:55:31 +02:00
LibretroAdmin 24429c4837 menu: what a combination does is picked from a list; an empty row can be pressed
Setting a combination by pressing needed a row that was already
something: what a combination is to do could only be typed, and
pressing on an empty row had nothing to put its buttons on.

What it does is picked from a list now - "What the Combination Does"
- of every hotkey but the enabler, by its name in the menu ("Menu
Toggle", "Pause"...), the commands that have no hotkey, and the
macros there are:

- Right on an empty row: hold the buttons together, let go, and the
  list opens; picking one makes the combination of those buttons
  doing it. Back out of the list and nothing is made.
- Left on a row with something in it: the list opens; picking one
  changes what it does and keeps its buttons.
OK still types the whole line, and Start still takes it out.

The list is the menu's own drop-down for a setting with options: one
setting, never shown as a row, whose options are the targets, made
again each time it is opened, and shown by their names. The setting
has a label of its own (input_combo_target) and a name and a line
under it in the English strings; the five commands' names in the
list are in English and not in the translations yet.

Two faults found on the way and not shipped: the setting's options
were this file's own buffer, and the menu's settings, freed, freed
it - the program aborted as the menu came up, which the frontend
harness, never freeing the menu's settings, did not show and a run
of the program did; and a capture on an empty row, with no entries
yet, was never given a frame, the frame's work for entries being
looked at only when there were entries.

What the frame does for entries is behind one byte now - there are
entries, a capture runs - and one call out of line: measured over
300 frames (X11), 3,445,236 instructions with no controller and
4,106,553 with one, against 3,445,260 and 4,106,635 before.

The frontend harness gains a lane, driving the menu with its own OK
and Back, as the configuration has them: the list holds every target
by its menu name, in order; buttons held on an empty row and Menu
Toggle picked make "l3+r3 : menu_toggle"; Left on that row and a
macro picked make "l3+r3 : macro_2"; and Back out of the list after
buttons held on another empty row makes nothing. The lane on
assigning by pressing no longer holds that an empty row starts
nothing.

Checked besides: every other lane passes; the X11 keyboard session
logs the same as before, the hotkey and quit-combination sessions
what master's binary logged, and the real-program sessions on entries
and on a macro do as before; the packed translation tables check.
2026-10-08 15:47:31 +02:00
LibretroAdmin 7d1081ccc8 libretro-common: r7z decodes a folder a slice at a time for a reader on another thread
A 7z folder decodes from its start to its end and no other way, and for
a large one that is a long time: tens of seconds for a disc image, all
of it before the first byte could be had.

r7z_archive_decode_step() does one slice of the folder that holds an
entry - the slicing was already there, for r7z_archive_extract_slice() -
and r7z_archive_decode_peek() says, from any thread, how much of an
entry has been decoded and where it is. A folder whose last coder is
LZMA or LZMA2 fills the buffer it will be held in from the front, and
what is in it is final, so the pointer is the same from the first peek
to the last and for as long as the folder is held; only the count grows.
A folder that ends in a filter gives nothing until it is done.

What is published is a state, a count and, written before the state
says so, the buffer and the folder: retro_atomic, no lock. It is
withdrawn wherever the held folder is replaced or handed over.

r7z_archive_folder_size() says what decoding a folder will take.

samples/formats/r7z: both test archives are decoded a slice at a time
with a peek after each - what is there is the member's bytes, in one
place, only growing - and end up as the folder a borrow gives. Passes
under ASan and UBSan.
2026-10-08 15:29:33 +02:00
LibretroAdmin ec0323a89d libretro-common: rrar reads an archive through a callback
The RAR reader could only be given the whole archive in memory. A
caller that could not map the file read all of it in and kept it for as
long as the archive was open.

rrar_archive_open_read() takes a positioned-read callback instead. The
headers are read one at a time at open - both walkers now look at a
header through a pointer to that header, which is the archive's own
bytes when it is in memory and a small buffer the header was read into
when it is not, in place of indexing one image of the whole file. A
stored member is read straight into the buffer it is extracted to; a
compressed one has its packed bytes read in for as long as it takes to
decode them.

Nothing changes for an archive in memory: the same bytes are looked at
where they lie.

samples/formats/rrar: every archive is checked both ways, and each of
the 300 cut and bit-flipped variants of nine of them has to get the same
verdict, the same members and the same bytes or refusal for each member
in memory and through the callback. Passes under ASan and UBSan.
2026-10-08 15:13:17 +02:00
LibretroAdmin 9b848bbd9c libretro-common: r7z reads an archive through a callback, and decodes a packed stream where it lies
Two things about how the 7z reader gets at an archive's bytes.

It could only be given the whole archive in memory. A caller that could
not map the file read all of it in and kept it for as long as the
archive was open, beside everything decoded from it.
r7z_archive_open_read() takes a positioned-read callback instead: the
header is read at open, and a folder's packed bytes are read in when
the folder is decoded and freed when it has been.

And with the archive in memory, a folder's packed stream was copied to
a buffer of its own before the first coder read it - as many bytes
again as the folder's compressed size, read once and freed. The first
coder now reads it where it lies. (A BCJ2 folder's four streams are
still copied.)

A 353 MB solid archive holding a 512 MB file, the file borrowed from the
decoded folder:

                            before                 after
  in memory (mapped)        peak 1220 MB           peak 866 MB
  not in memory             865 MB held, 1220 peak 512 MB held, 866 peak

The time is the LZMA decoder's and does not move: 25.1 s before, 24.6 s
after, on one slow core (7-Zip itself takes 22.5 s there).

samples/formats/r7z: every check of the detach test also runs with the
archive read through a callback; no callback and a file shorter than it
says are refused. Passes under ASan and UBSan.
2026-10-08 15:05:19 +02:00
LibretroAdmin 2a45c45a37 video_thread: a core that keeps its Vulkan context keeps a live interface across a video restart
A core can ask the frontend to keep its hardware context when the video
is restarted (cache_context). For Vulkan the device is kept, the core is
not told of the restart - neither context_destroy nor context_reset is
called - and it goes on with the render interface it was handed. The
Vulkan driver keeps that interface in static storage for exactly this
(vulkan_hw_iface). The threaded wrapper did not: the interface it hands
a core lived in the wrapper's frame ring, with the wrapper as its
handle, and both were freed at the restart. The core's next call went
through a dead pointer - an access violation in hw_get_sync_index, or
frames handed to a ring nobody presents from.

The wrapper's Vulkan interface is now in static storage too. Its handle
is cleared when the wrapper goes, so a call in between finds no ring and
does nothing, and it is filled in again for the next wrapper.

Nobody asks for it again after a restart that did not reset the core,
so the frontend does: video_driver_hw_render_interface_kept() remembers
the interface a core was handed, and after such a restart asks the
driver in front for it once more - which is what brings the wrapper's
up to date - and checks that it is the same one. If it is another
interface altogether (threaded video was switched across the restart:
the driver's and the wrapper's are different objects), the core cannot
go on with what it holds, and is reset after all, its objects destroyed
first on the device that was kept. A core that holds no interface - an
OpenGL one - is not affected.

Nothing on the frame path changes: the core is handed the same entry
points with the same handle, from a different address. The cost is one
pointer stored when the interface is asked for and one comparison per
restart.

Run with flycast made to set cache_context, on Vulkan, the video
restarted twice (fullscreen and back) and a screenshot after each:

  threaded video, before          dies at the first restart
  threaded video                  the picture after both; context kept twice
  no threaded video               the same
  interface forced "not kept"     the core is reset and the picture is there
  a core that resets as usual     unchanged

Not run: threaded video actually switched across a restart (the reset
path above was reached by forcing the check), and the Direct3D
interfaces under the wrapper, which still live in the ring - a core
keeping a D3D context there is reset through the same check unless the
new ring happens to land at the old address.
2026-10-08 14:46:02 +02:00
LibretroAdmin 38d7bdf492 menu: assigning a combination by pressing pushes no list; the menu is held off while it runs
Found on a real run: Right on a combination's row brought up "Select
File" - a file browser at the list of drives - and the menu stayed
there after the buttons were pressed and let go.

The patch before this gave the capture a screen of its own by
pushing a list onto the menu's stack, as "find a button by pressing
it" does from its OK. Pushed from inside a row's Right, the list came
up as a file browser, and the screen the menu should have made of it
- the message, and the menu held off - never came, so nothing took
it down. The frontend harness started the capture by calling the
row's handler itself, outside the menu's own handling of a press,
and saw the list it expected on the stack.

Nothing is pushed now. For as long as a capture runs, the menu is in
the state "find a button by pressing it" puts it in, over whatever
list is up: that list faded behind the message, and no input taken
by the menu. When the capture ends the menu is as it was. The row's
Right only starts the capture, and does nothing to the menu.

The harness's lane holds that while it runs nothing has been pushed
and the menu is held off, and that afterwards the list is where it
was and the menu takes input again. Every lane passes; the Android
unity build compiles.

Not run: the menu driven by a real press of Right on the row - the
harness still starts the capture through the row's handler.
2026-10-08 14:11:14 +02:00
LibretroAdmin 94aa371e7d menu: assigning a combination by pressing has a screen of its own, and the menu takes none of it
Found on a real run: with a combination being assigned by pressing,
OK and Back pressed for it were taken by the menu as well. The
capture took the RetroPad's buttons out of the frame's input, but the
menu went on taking input of its own, and nothing on the screen said
that pressing buttons now meant something else.

It is given what "find a button by pressing it" has: a screen pushed
over the list, which fades the list behind a message - "Combination
N: hold its buttons together, then let go. (5)", counting down while
nothing has been pressed, then "let go of all of them to set it" -
and the menu's binding state while it is up, under which the menu
takes no input at all. When the capture ends, by being let go or by
giving up, the screen goes and the list is where it was. The message
on the screen takes the place of the notification that started it.

The message is in English and is not in the translations yet, as the
rows' names are not.

Nothing on a frame: over 300 frames (X11), 3,445,260 instructions
with no controller and 4,106,635 with one, against 3,445,236 and
4,106,553 - the menu's file was built anew, and none of what changed
runs with no capture.

The frontend harness's lane on assigning by pressing now holds that
its screen is up over the list while it runs; that OK and Back
pressed during it neither leave nor move the menu, and are gathered
into the combination with the rest; and that the screen is gone when
it ends. Every lane passes.
2026-10-08 13:55:57 +02:00
LibretroAdmin b921899df3 quit: a quit from the frame loop waits for its save without freezing
retroarch_main_quit() pushed the auto save-state and then waited for
it inside the call, so the window sat on one frame for as long as a
large state took to compress and write. It now has two halves around
that wait. CMD_EVENT_QUIT - the menu, the quit hotkey, the window's
close button - runs the first and, while a state task is still in
flight, returns to the frame loop: runloop_iterate() presents the last
frame and the "Saving state" notice, runs neither the core nor the
menu, and finishes the quit once the task is through. Where the loop
cannot carry the wait it blocks as before: with no window to present
into, on the Switch and 3DS whose frame pumps the system's own
messages, and in the quits the operating system forces, which call
retroarch_main_quit() directly.

Both forms wait for a load state task as well as a save: a save over
an existing file first loads it into the undo buffer, and the save is
only pushed from that load's callback, so a quit asking for the save
alone could finish before the save had started and exit without it.

content_load_test gains a lane that quits while a save over an
existing state is in flight: the command returns with the task still
running and shutdown not begun, frames are presented and retro_run is
not entered while it writes, and the loop ends only once it is
through. It runs last, as the session ends with it.
2026-10-08 13:54:26 +02:00
LibretroAdmin 91ad18af98 libretro-common: rzip_seek_view, the bytes of an indexed member where they lie decoded
rzip_seek_read() copies out of the span decoded around a read. A reader
that only copies the bytes on again - a disc sector on its way to the
drive's buffer - can have them where they are: rzip_seek_view() gives a
pointer into the span, good until the next call on the index, or says
there is none when the bytes lie across two spans.

A decoded 2352-byte sector costs 28 ns handed over this way against
237 ns copied out. The sample checks views from everywhere against a
whole extraction, and that most reads get one.
2026-10-08 13:49:01 +02:00
LibretroAdmin deb26a3fba menu: the combination and macro rows show what they hold, and the list follows what there is
Found on a real run: a combination typed into its row and entered was
kept - after going out of the list and back in, the empty row had
moved on to "Combination 2" - but the row showed nothing of it, and
the list did not change until it was left and entered again.

The rows were listed with the entry type of an action. A row of that
type is drawn with no value at all, whatever its setting says. They
are listed as the text settings they are, with no type given, and
are drawn with their value: what that number is, as the input keeps
it.

And a row set or taken out has the list made again, not only drawn
again, so the empty row to add the next with moves on at once, or
back.

The frontend harness's lane on the rows checked which rows the list
had, and not what kind of row: it now holds that none is listed as
an action, and that entering a row asks for the list to be made
again. Every lane passes.
2026-10-08 13:37:06 +02:00
LibretroAdmin 806ffa8fde input: a combination's buttons are set by holding them, from its row in the menu
A combination's row in the hotkeys list was a line to type. Right on
it now sets its buttons by holding them, as a bind is set by pressing
it:

- the press that asked for it is let go of first, and is not taken
  as part of it;
- what is then held, pressed together or one after another, is
  gathered for as long as anything is held;
- when all of it is let go, those are the combination's buttons; its
  key, its time held and what it is stay as they were.

Five seconds with nothing pressed gives it up and changes nothing.
While it runs, the RetroPad's buttons it reads are taken out of the
frame's input, so the menu does not move under it and nothing else is
pressed by them. A row with nothing in it starts nothing (type it
first: OK on the row); an entry taken out while it runs ends it.

A row now shows what its number is as the input keeps it, asked each
time it is drawn, since more than the row can change it.

It runs inside the frame's test for entries - there is one to assign
only when there are entries - so with none there is nothing more on a
frame. Measured over 300 frames (X11), 3,445,236 instructions with no
controller and 4,106,553 with one, against 3,445,256 and 4,106,520.

What it says on the screen - "Hold the buttons for the combination,
then let go." and "Nothing was pressed: the combination is as it
was." - is in English and not in the translations yet, as the rows'
names are not.

The frontend harness gains a lane, in the menu with the stand-in
controller: a row with no entry starts nothing; Right held as it
starts is not taken; L3, then L3 and R3, then R3, then R3 and Down,
let go, make "down+l3+r3", the key, the time and the target kept;
the menu's selection is where it was; five seconds of nothing gives
it up with the combination as it was; and the entry taken out while
it runs ends it.

Checked besides: every other lane passes; the X11 keyboard session
logs the same as before, the hotkey and quit-combination sessions
what master's binary logged, and the real-program session on entries
does as before.

The last part of the mapping compiler's ninth step (plan, section
21).
2026-10-08 13:26:30 +02:00
libretroadmin b941baed74 audio: a fast-forward limit past the audio's fastest tempo is not held to it
With Speedup or pitch-preserving playback, Audio Sync and a fast-forward
limit, the threaded pipeline's producer waits on a full ring so the audio
sets the tempo. The audio plays at most AUDIO_MIN_RATIO's sixteen times
real time, so a limit above that ran at sixteen times instead of its own:
20x and 50x both ran at 16x. Such a limit no longer waits; the audio
plays at its fastest and the rest is dropped, as with no limit at all.
Limits up to sixteen are unchanged.

samples/audio/ff_speedup runs the loop - limiter, producer wait, a
device draining at the published multiplier - and pins 4x, 16x, 20x and
50x reaching their limits.
2026-10-08 13:24:16 +02:00
LibretroAdmin a24ac15fb6 menu: combinations and macros are rows at the end of the hotkeys
Entries (input_combo_N) and macros (input_macro_N) could only be
written in the configuration file. They are rows in the menu now, at
the end of Settings > Input > Hotkeys: each combination there is,
then each macro there is, and after each kind one empty row to add
the next with.

A row is a line of text, edited with the keyboard, in the same words
as the file: "l3+r3 : menu_toggle", "down 2, down+right 2, a 4".
What is entered is read as an entry or a macro and the row then shows
it as kept, in the form the file will have; what does not read
changes nothing - the row shows what was there, and the log says what
it wants. An empty line, or Start on the row, takes that number out.
Changes are saved with the configuration, as the patch before this
made them.

The rows are sixteen text settings of each kind, made with the
hotkeys' settings and listed by name; only the ones there is
something in, and one empty one of each kind, are shown.

Their names are in English and are not in the translations yet:
"Combination N" and "Macro N". The hotkeys' list in the Qt and
desktop companion interfaces does not show them.

Nothing on a frame: measured over 300 frames (X11), 3,445,256
instructions with no controller and 4,106,520 with one, against
3,445,267 and 4,106,520.

The frontend harness gains a lane: a row shows its entry or macro as
written; what is typed into a row and entered is taken, and the row
shows it as kept; what does not read changes nothing, in a row with
something in it and in an empty one; an empty line takes the number
out, and so does Start; and the hotkey list itself, built as the
menu builds it, holds each combination and macro there is and one
empty row of each kind.

Checked besides: every other lane passes; the X11 keyboard session
logs the same as before, and the hotkey and quit-combination sessions
what master's binary logged.

The second part of the mapping compiler's ninth step (plan, section
21). Assigning by pressing is next.
2026-10-08 12:45:43 +02:00
LibretroAdmin a69980050e input: entries and macros can be changed while running, and are saved
An entry or a macro was whatever its line of the configuration said
when the configuration was read, and nothing could change one or
write one out. A menu for them needs both.

By its number - the N of input_combo_N, input_macro_N - one can be
set, which replaces the one of that number if there is one; taken
out; and written out as the configuration reads it:

   "  r3 + l3:menu_toggle "  is  "l3+r3 : menu_toggle"

buttons by their binds' names in the order of their numbers, then
the key, then the time held, then what it is. One read back from how
it was written is the same one. A macro taken out or replaced hands
back its steps' room in the pool; whatever was playing stops, since
the macros after it move up.

Saving the configuration writes the entries and macros there are,
and takes out the line of one there is not, so that one removed in
the program stays removed. One kind of line is neither: a line that
did not read - somebody's slip, "start : puase". It was said in the
log when the configuration was read; a save leaves it in the file as
they wrote it, to be put right, until that number is set or taken
out.

Nothing on a frame: measured over 300 frames (X11), 3,445,267
instructions with no controller and 4,106,520 with one, against
3,445,252 and 4,106,520.

The frontend harness gains a lane: each of five entries and three
macros is written out as what it is, and reads back the same; a
number set again is replaced, and one that does not read replaces
nothing; one taken out leaves the others, and its steps are room
again - the pool filled to the brim and emptied; a save writes what
there is and takes out the line of what there is not; what was saved,
loaded, is what there was; and a line with a slip in it survives a
save, and goes when it is taken out.

Checked besides: every other lane passes; the X11 keyboard session
logs the same as before, the hotkey and quit-combination sessions
what master's binary logged, and the real-program sessions on
entries and on a macro do as before.

The first part of the mapping compiler's ninth step (plan, section
21): what a menu for entries and macros stands on.
2026-10-08 12:05:14 +02:00
libretroadmin b655a205b1 runloop: fast-forward's frameskip decides before the core runs, and a core that asks need not render a dropped frame
The frameskip picks the frames it presents at the top of the iteration
instead of in video_driver_frame, with the same accumulator, target and
forced render after a dupe; video_driver_frame consumes the decision, or
makes it itself for a frame no decision waits for. GET_AUDIO_VIDEO_ENABLE
then leaves video out for a frame that will be dropped, so a core that
honours it skips the render instead of spending the GPU on a frame
nobody sees. Under threaded video a hardware core rendering at a high
internal resolution could queue so far ahead of the presenter that
presents stalled for seconds in fast-forward. When a render turn lands
on a dupe, the core is asked for video until a real frame arrives, as
the forced render presented it before.

samples/runloop/pacing pins the accumulator step and the owed-frame rule.
2026-10-08 11:46:42 +02:00
LibretroAdmin 7954cf6579 input: a macro goes by the frames the core runs, stepped ones too; not started in netplay
A macro stood still on a poll made while the core was paused or
idle, told by the run loop's flags. But a frame stepped while paused
is run with the pause flag still up: the macro did not move on a
stepped frame. And a netplay stall polls without running the core
with neither flag up: the macro moved on with no frame run.

The run loop says it instead. It polls input in nine places where no
frame of the core follows - six in its state check (the menu, idle,
paused, a rewind held while paused, the window not focused), and
three with no core to run (a content load in stages, a close, a
netplay stall). Each makes its poll through
input_driver_poll_between_frames(), and a macro stands still for
those and for no other. None of the nine is on the path of a frame
the core runs: over 300 frames (X11) the count is the same to the
instruction, 3,445,252 with no controller and 4,106,520 with one.

Tried under what changes how frames and input go, by a new lane in
the frontend harness that takes what the core is given frame by
frame:
- frames stepped one at a time while paused: the macro as written;
- run-ahead of two frames, the core run 120 times for 40 frames
  shown: as written - the frames run behind the one shown are run
  without a poll;
- a replay recorded while it plays: as written; and that replay
  played back, with no macro started: the same presses again;
- netplay, hosting with nobody connected: the macro was never given
  to the core.
So with netplay on a macro is not started, as with "Remap Controls"
off. Netplay takes the player's input for itself, and not at the
poll a step is taken at; what a macro should be in a netplay game is
not settled here.

Two things the lane had to learn, neither of them the program's
fault. A replay that plays to its end pauses the core, as it is meant
to, so the lane stops its playback short of the end. And frame
advance puts the run loop in a pause of its own kind that only the
pause hotkey ends; left in it by a plain "unpause", the lane on key
events that runs later lost a fifth of its events. The lane leaves
its stepped pause by the hotkey.

Checked besides: every lane passes, run twice over; the X11 keyboard
session logs the same as before, the hotkey and quit-combination
sessions what master's binary logged, and the real-program session
with a macro in its configuration gives the core B, nothing, A,
nothing as before.
2026-10-08 11:44:20 +02:00
LibretroAdmin a72d9ddf15 libretro-common: rzip_seek, a deflated zip member read at any offset without decoding it whole
A deflate stream decodes only from its start. Reading the end of a large
member - a disc track in a zip - meant decoding all of it, and keeping
all of it in memory for the next read.

rzip_seek keeps restart points along the stream instead: at a block
boundary, the bit position in the compressed bytes and the 32 KiB of
output before it are all the inflater needs to start there
(rinflate_set_dictionary, _set_start_bit, _tell_bits and stop-at-block
were made for this). With a point about every megabyte, any byte is
within one span's decoding of a point; the spans last read are kept
(eight of them), so reading on in order decodes each span once.

The points are found by decoding the member through once,
rzip_seek_build(), which can be done a piece at a time and on a thread
of its own: one thread builds, one reads, and they share only the
points already published (a count, stored with retro_atomic). The
member's checksum is verified when the build reaches the end.

For a 512 MB member (350 MB deflated), on one slow core: indexed in
1.4 s; a 2352-byte sector from anywhere in 2.7 ms; sectors in order at
370 MB/s. The index is 16 MB and the span cache 8 MB, where the member
decoded whole is 512 MB.

samples/formats/rzip: rzip_seek_test makes an archive, indexes it a
piece at a time and compares reads from everywhere - from memory and
through the read callback, at two spans - with a whole extraction; a
damaged member is refused. Given an archive, it checks every deflated
member of that; with -t it times the largest. Passes under ASan and
UBSan.
2026-10-08 11:29:11 +02:00
LibretroAdmin 6400e21b86 input: macros - buttons pressed for a user a step at a time, started by an entry
A macro is steps, and a step is some of the RetroPad's buttons held
for a number of frames. It is a line of the configuration, and an
entry starts it:

   input_macro_1 = "down 2, down+right 2, right 2, a 4"
   input_combo_4 = "r3 : macro_1"

Steps have commas between them; a step is buttons by the names their
binds have, with + between, or - for none, and then its frames.
"macro_1" plays for the first user, "macro_1@2" for the second. Up
to sixteen macros, thirty-two steps to one, and 256 steps among them
all: the steps are in one pool, so room goes by what is written. A
line that does not read as one is said so in the log and left out.

The frames are the core's: the ones it runs, not the screen's, and
not a time. A core reads the pad once for each frame it runs, so a
macro is the same presses to the game at any speed and on any
screen. Input is polled with the core paused, too, for the hotkeys;
a macro stands where it is then, and goes on when the core does.

It is played where a remapped button goes, the mapper, so the core
is given it as it is given any press, added to what the user holds.
A user with a macro playing has remap work at the poll, which is
where a step is taken: the bit that says so is the one a remapped
button sets, so with no macro playing nothing is asked on a frame
and nothing is done - measured over 300 frames (X11), 3,445,220
instructions before and 3,445,252 after with no controller,
4,106,514 and 4,106,520 with one. With "Remap Controls" off nothing
goes through the mapper, and a macro is not started.

The frontend harness gains a lane, by what the core is given frame
by frame, read a button at a time and as a mask: each step's buttons
for its frames and no more; added to what is held; for the user it
was started for; standing still through a pause longer than the
macro and then pressing for exactly what was left; not started with
Remap Controls off; once for a press of its entry however long that
is held; only what reads as a macro taken, of thirty-two steps at
most, while there is room in the pool. A session with the real
program reads a macro and an entry from its configuration, and on
the entry's key the core logs B, nothing, A, nothing - the macro -
with no button touched.

Checked besides: every other lane passes; the X11 keyboard session
logs the same as before, and the hotkey and quit-combination sessions
what master's binary logged.

Not looked at: a macro playing under run-ahead, in netplay, or while
a replay is recorded; and a stall in netplay polls without running
the core, which the pause rule does not cover.

The mapping compiler's eighth step (plan, section 21).
2026-10-08 10:03:13 +02:00
LibretroAdmin b40fe96a40 libretro-common: rzip_archive_extract_into, a member decoded into the caller's memory
rzip_archive_extract() hands back a member in a buffer it allocates. A
caller with a place of its own for the bytes - a cartridge's memory, a
texture's - then copies them there and frees the buffer: the member is
in memory twice for a while, and written twice.

rzip_archive_extract_into() decodes into memory the caller has, with the
same checks (method, range, checksum). A member longer than the room
given is refused and nothing is written. rzip_archive_extract() is now
an allocation and a call to it.
2026-10-08 09:22:21 +02:00
LibretroAdmin 97fedfd775 input: the fixed controller combinations are a table, looked at with the entries; an entry can be timed
The "Menu Toggle" and "Quit" controller combination settings each
choose one of ten fixed combinations of the RetroPad's buttons. The
run loop looked at both itself, every frame: for the menu's it copied
the frame's 56 bytes of input bits aside, whether a combination was
set or not, and with one set it called out to take an overlay's
buttons from the copy and again to test it; the quit one it tested
further down. The ten were a branch each, a test a button.

They are a table of button masks, and both settings are looked at in
one place, at the end of the frame's input pass with the entries:
the menu's presses the menu toggle, the quit one presses the quit
hotkey. With neither set that is one test. With one set and its
buttons not all down - nearly every frame - it is a few more; the
closer look, with the overlay's rule and the copy it needs, is taken
only when the buttons are down, or for the two combinations that are
one button held for two seconds, which have to be told every frame.
The run loop's two blocks, its copy and its bookkeeping for the quit
combination go; the settings and what they do are as they were.

As they were to the letter: the menu's does not count buttons an
overlay alone holds when the overlay has a menu button; the quit one
does not count a button that is being held back until it is let go,
having been looked at after those were taken out of the frame; the
two timed ones keep their clock and their rule with "Hotkey Enable".

An entry (input_combo_N) can be timed too:

   input_combo_3 = "start held 2s : exit_emulator"

"held" and seconds to a tenth, up to 25: the sources must stay down
that long first, by the clock and not by a count of frames, and it
is given once for a hold.

Measured over 300 frames (X11), with the run loop's state check
counted along with the input work, since that is where this was:
- neither combination set: 4,887,099 instructions before, 4,880,558
  after;
- the menu's set to L3+R3: 4,895,008 before, 4,888,006 after.
The input work alone, as the earlier patches measure it, is five
instructions a frame more - the test that moved into it.

Checked: the frontend harness's lanes on the menu combination (the
overlay's rule) and the quit combination (Confirm Quit, a held
combination reporting once) pass unchanged; its lane on entries
gains what reads as a time, and that a timed entry is given by the
clock however many frames pass, once, and not when let go early. The
session that quits by L2+R2 through the setting logs what master's
binary logged; so do the hotkey session and the X11 keyboard one;
the real-program session on entries does as before.

The mapping compiler's seventh step (plan, section 21). The two
settings stand for one of ten entries each and are not stored twice:
no line is written for them, so there is one place a combination is
set.
2026-10-08 09:21:18 +02:00
LibretroAdmin 4c12d22f9d videoproc: the audio callback reads the capture handle without a lock
The frontend's audio thread calls the core's audio callback back to
back, and RetroArch ignores SET_AUDIO_CALLBACK(NULL), so the main
thread opens, swaps and closes the ALSA capture handle under it. The
handle's use is now one atomic word: bit 0 set while the handle may be
read, the rest a count of callbacks reading it. A callback joins by
CAS only while the bit is set; opening publishes the handle and then
sets it; closing clears it, which turns every later callback away, and
waits out the read in flight - a capture period at most - before the
close. The callback takes no lock, and a close is no longer held off
by a callback that keeps re-taking the mutex.

samples/cores/videoproc_audio includes the core and runs its callback
back to back on a reader thread while the main thread opens and closes
ALSA's null PCM 200 times, with the capture read a wrapped stand-in:
no read is made on a closed handle - checked when the read starts and
when it ends, so a close landing mid-read counts - and the callback
takes no pthread mutex. It runs in CI plain, under ASan and UBSan, and
under TSan.
2026-10-08 09:13:39 +02:00
LibretroAdmin d16f0c5122 switch_audren: the renderer stays on the writer's thread, without a lock
libnx's audrv is not thread-safe, and write_avail() was the one call
the frontend made from another thread - rate control's fill, every
frame, while the audio thread wrapper writes - which is what the mutex
around every renderer call was for, an audrvUpdate() service call
included. The writer now publishes the room in an atomic after every
append and update, and write_avail() from any thread but the writer's
reads that instead of calling into the renderer; on the writer's own
thread, the frontend writing without the wrapper, it updates as
before. Write, wait_writable, start and stop record their thread. A
non-blocking write that finds no free buffer updates once and takes
what that frees, as the update write_avail() made before every write
used to, and goes on filling while buffers are free.

samples/audio/switch_audren runs the driver's libnx build against a
stand-in <switch.h> whose renderer thread plays a wave buffer a frame:
every renderer call comes from the writer while another thread reads
write_avail(), a write_avail() on the writer's thread sees what played,
non-blocking writes keep taking audio, stop and start pause and resume
the voice. It builds with MinGW as well, and runs in CI plain, under
ASan and UBSan, and under TSan.
2026-10-08 09:04:12 +02:00
LibretroAdmin d5149fc80b content: a core that takes archives itself has its files named after the archive
A path into an archive - "game.zip#track03.bin", which is how a scanned
playlist lists content it identified by a file inside one - names
everything after the member: states, saves, cheats, per-game overrides
and remaps. For a core the member is extracted for, that is the point:
the member is the content.

A core that sets block_extract is handed the archive as it is and
decides for itself what in it is the content. The member the playlist
happened to match is not what is loaded, and for a disc image packed
with its tracks it is "track03.bin" for every game there is: all of
them shared track03.state, track03.cfg and the rest.

Once the core has said it takes archives itself - right after its
system info is read, before the overrides are looked for - the name is
taken from the archive: "game". It is the name the same archive already
gets when it is loaded by its own path, so both ways of starting it now
share their files.

Run with flycast (block_extract) and its test disc in a zip and a 7z
named for a game, loaded as "<archive>#track03.bin" with a state saved
on the way out: the nightly writes track03.state.auto, this writes
"<archive name>.state.auto" - as both do for the archive's own path.

States and saves already written under the member's name are not found
under the new one; they were shared by every archived game of the core.
2026-10-08 08:56:26 +02:00
LibretroAdmin 57d55c280a pulse: the writer hands audio to the server's thread without the mainloop lock
write(), write_avail() and wait_writable() fill and measure a
retro_spsc ring on the frontend's thread and take no lock. The
server's thread moves the ring into the stream from the write
callback, under the mainloop lock it already holds for every
callback, and from a pipe the mainloop watches: a drain that leaves
the ring dry with the stream still wanting more flags it, and the
next write wakes the server's thread through the pipe, since no write
callback comes until the server consumes. A blocking write parks on
an eventcount every drain notifies, bounded as before. The room the
frontend sees is the stream's writable size less what the ring holds,
so the buffer it measures against is the server's, as it was. The
mainloop lock stays on cork, uncork, connect and the latency query.

The ready flag, buffer size and minreq the server's thread changes
are atomics. PulseAudio now needs threads at configure time.

live_drivers counts the driver's pa_threaded_mainloop_lock calls at
the link boundary across 200 blocking writes and their write_avail
and wait_writable calls and requires none, and runs under TSan in CI
with libpulse's own pa_mutex_lock suppressed; the lock budget counts
pa_threaded_mainloop_lock and pins pulse_write, pulse_write_avail and
pulse_wait_writable at zero.
2026-10-08 08:55:25 +02:00
LibretroAdmin e49b629855 input: a hotkey or a command can be reached by buttons and a key held together
A hotkey has one bind: a key, and a button or an axis. Nothing else
could reach one, but for ten fixed combinations of controller buttons
for the menu and for quitting; and a command that has no hotkey -
undoing a state load, say - could not be reached from input at all.

An entry says "these, held together, are that". It is a line of the
configuration:

   input_combo_1 = "l3+r3 : menu_toggle"
   input_combo_2 = "key_f9 : undo_load_state"

On the left, buttons of the RetroPad by the names their binds have
(b, y, select, start, up, down, left, right, a, x, l, r, l2, r2, l3,
r3), a key as key_ and its name in a bind, or both, with + between.
On the right, a hotkey by the name its bind has, or one of the
commands that have no hotkey and need nothing told them: pause,
unpause, undo_load_state, undo_save_state, save_config. Up to
sixteen. A line that does not read as one is said so in the log and
left out.

They are gone through once a frame, after the ports have been, with
what is held of the RetroPad as the fixed combinations have always
had it. A hotkey an entry reaches is down while the entry's sources
are, and does what its own bind would. A command is given once, when
the sources go down, and carried out when the frame's input has been
gone through, since a command may start the drivers again. What
holds a hotkey back holds an entry back: "Hotkey Enable" not being
held, for the kind of source - controller or keyboard - that has an
enabler; the keyboard being held by Game Focus or a line of text;
input waiting to be let go.

With no entry there is one test a frame. Measured over 300 frames
(X11) it is two instructions a frame: 3,443,109 to 3,443,711 with no
controller, 4,104,418 to 4,105,009 with one - after two tests on the
hotkeys' port, a little earlier in the same function, were made one
to pay for most of it.

The frontend harness gains a lane: only what reads as an entry is
taken, and sixteen at most; a command is given once when all of its
sources are down; a hotkey reached is pressed once for a press
however long; the controller's enabler holds back an entry of
buttons and not one of a key; a held keyboard holds back an entry of
a key. A session with the real program reads four lines from its
configuration - a key that pauses, one that unpauses, one for the
pause hotkey, and one that is not an entry - and does what they say,
the last being named in the log.

Checked besides: every other lane passes; the X11 keyboard session
logs the same as before, and the hotkey and quit-combination sessions
what master's binary logged.

The mapping compiler's sixth step (plan, section 21). Not here yet:
a time the sources must be held, the ten fixed combinations as
entries (the seventh step), and a place in the menu (the ninth).
2026-10-08 08:50:23 +02:00
LibretroAdmin 95a0f72312 input: the turbo and hold binds are not read on ports where nothing can press them
The poll read two binds for every port at every poll: the one that
turns turbo on (when turbo is enabled) and the one that turns "hold"
on. Each read is a call into the state path. Nearly nobody has
either bound to anything, and a bind with no key, no mouse button
and no pad behind it cannot be pressed.

What is kept with a port's keys gains one more set of bits, made
when the binds change: for each bind, whether it is usable and a
key, a mouse button or some controller's pad may press it. When the
poll has made a port's keys current, a bind of the kind only those
can press - not one of the RetroPad's sixteen, which a remote pad
can press too - is not read if none can. Otherwise it is read as
before.

Measured, the frame's input work over 300 frames (X11):
- no controller: 3,562,145 instructions before, 3,443,109 after;
- a controller with a hotkey enabler: 4,219,812 before, 4,104,418
  after.
Against master before the two patches: 3,556,571 and 4,215,974.

Checked: the frontend harness passes every lane, and its core-view
digests - which include turbo and hold bound to pad buttons, on and
toggled - are the ones master's run gives. The X11 keyboard session
logs the same as before; the hotkey and quit-combination sessions
log what master's binary logged.
2026-10-08 07:59:41 +02:00
LibretroAdmin 8a5dbe6fe6 input: a user's binds are a row that is shared until it is written
The binds were two tables, one of what each of sixteen users has
configured and one of what the profile of the controller on each of
sixteen ports gives: 25,088 bytes. Most of the rows hold nothing of
their own. A user who has configured nothing has the defaults, the
same ones as the next such user; a port with no controller, or a
controller with no profile, has nothing behind any bind.

The tables are sixteen pointers each. They start at three shared
rows - the first user's defaults, every other user's, and "no
profile" - and a row becomes its user's or its port's own, a copy,
at the first write to it. The write calls
(input_config_bind_edit(), input_autoconf_bind_edit()) do that, and
are now functions; readers index the tables as they did. Three things
that wrote every bind of every user write only what changes:
- the configuration's binds are read into a copy of each bind and
  written if the file changed it;
- putting the binds back to their defaults makes the two shared rows
  and writes them into the rows that are someone's own;
- forgetting a controller's profile (input_autoconf_binds_none())
  writes "nothing" into the port's own row, if it has one.
A row that has become someone's own is never freed or moved, so an
address taken of it is good for as long as it was before.

The binds take 3,392 bytes with nothing configured, and 784 more for
each user who configures a bind and each port that gets a profile:
the build's uninitialised data falls by 21,664 bytes.

Measured, the frame's input work over 300 frames (X11) rises a
little with this alone - 3,556,571 instructions to 3,562,145 with no
controller, 4,215,974 to 4,219,812 with one - a row being a pointer
to follow where it was an address to work out. The patch after this
takes back a great deal more than that in the same loop.

The frontend harness's lanes write binds where they are kept, so it
gives every user and port a row of their own at its start but for
two, and gains a lane on those two: they share a row; reading parts
nobody; a write gives the one written their own, which says what the
shared one did but for the write, and no one else's changes; a
forgotten controller's row is kept and reads as nothing. Six
standalone samples that define the tables define them as pointers;
the one that uses them gives each user a row.

Checked: every lane passes; the X11 keyboard session logs the same
as before, the hotkey and quit-combination sessions what master's
binary logged; samples/input/winraw_keyboards (under Wine) and
udev_devices pass.

The mapping compiler's fourth step (plan, section 21). What is stored
is rows on demand and not a list for each bind: it meets the step's
estimate - about 5 KB for one player with one controller, against
75,264 bytes before the step - with every reader left as it was.
2026-10-08 07:59:37 +02:00
LibretroAdmin 684198e440 gfx_widgets: a task widget still waiting takes a new title without an animation
A task's widget waits in task_pending until there is room for it on
screen. An update from the task finds it there as well as on screen,
and a changed title started the title's slide animation either way.

A waiting widget is not drawn, so the animation showed nothing. It
also outlived the widget when the waiting widgets were discarded:
gfx_widgets_task_pending_discard() frees them without looking for
animations - they "were never animated" - and the animation, on
finishing, wrote its last value into the freed widget. The discard
runs when the widgets are taken down, which a restart of the video
driver does: a fullscreen toggle, or a core asking for a larger
frame, a moment after a save state.

A widget that is still waiting now takes the new title as it is,
like one with the alternative look. Nothing is animated for it, so
there is nothing to cancel when it is discarded.

Found with AddressSanitizer: heap-use-after-free in
gfx_animation_update_in(), 52 bytes into an 88-byte block - a
disp_widget_msg_t's msg_transition_animation - freed by
gfx_widgets_task_pending_discard() from gfx_widgets_detach_tasks(),
gfx_widgets_deinit(), driver_uninit(), video_driver_reinit(). On a
release build it showed as "double free or corruption" at some later
free.

Reproduced with a core that asks for a video restart ten times, 25
frames apart, while states are saved and loaded by network command:
the report twice in two runs before this, none in two after.
2026-10-08 07:57:47 +02:00
LibretroAdmin 49191cdae5 rrar: the PPMd model is written for libretro-common
rrar_ppmd7 is PPMd var.H in this tree's conventions: C89 with snake_case
types, 32-bit offsets into the model's memory on every target, the
one-symbol context's state as a union member and free-list links copied
rather than type-punned. RAR's range decoder is inline and reads a span
of the packed data, which the RAR 2.9 reader hands to a PPMd block and
takes back after it. A binary decision takes no division, and the escape
path, out of line from the common ones, counts the symbols a context has
left without building a list of them.

The reader's test adds libarchive's ppmd_lzss_conversion.rar, whose PPMd
blocks give way to LZ blocks and back.
2026-10-08 07:50:06 +02:00
LibretroAdmin c74e06f397 CI: the Android build rides out a dependency server's errors
Gradle's dependencies are cached between runs, a repository answering
with a server error is retried with a 2 s doubling backoff over six
tries instead of three within a quarter second, and a build that
still fails to fetch a dependency is run again, up to three times,
keeping what it already fetched and built. A build that fails for
any other reason fails the step at once.
2026-10-08 07:48:08 +02:00
LibretroAdmin 20e7caf1da menu: a bind's setting says whose bind and which, and holds no address of it
Each bind has a setting in the menu, about 1,600 of them, made when
the menu's settings are. Each held the address its bind was stored
at. So did a capture of a bind, from the frame it began until the
button came. And the bind's number, where it was wanted, was worked
back out of the address by arithmetic on the table.

That is what keeps the binds a table with a place for every bind of
every user: every one of those places is pointed at from the moment
the menu starts.

A bind's setting already says whose bind it is (index_offset) and
which (bind_type). It goes by that: what it shows, clearing it and
resetting it ask for the bind when they want it. A capture carries a
user and a number instead of an address, reads the bind's current
value through them, and writes the captured one the same way - which
counts the write as a change when it is made, where it had been
counted when the address was taken. The pointer is gone from the
setting, input_config_bind_id() from the input's header, and nothing
outside the input's files holds a stored bind's address past the
statement that asked for it.

No change to what the menu does. The frame is untouched: 3,556,571
and 4,215,974 instructions over 300 frames (X11), as before.

The frontend harness gains a lane on it: a bind's setting shows the
bind as it is now, clearing it clears that bind and not another
user's, and a button captured for it - with the stand-in controller,
through the menu's own capture - lands in it. Every other lane
passes; the X11 keyboard and hotkey sessions log as before.

Noticed and left as it is: when a capture of all of a user's binds
moves on to the second bind, it takes the user's controller number
for the user. With a controller number other than the user's own -
user 2 on controller 1 - the rest of that user's binds would be
written to another user. It did so before this, by the same number.

Groundwork for the mapping compiler's fourth step (plan, section
21): with no address held, the binds can stop being a table.
2026-10-08 07:34:57 +02:00
LibretroAdmin 67e1d30b5b input: what binds are called is a list of the binds that are called anything
A controller's profile names some of its buttons and axes ("Cross",
"Left stick right"), and a user's bind takes a name over when the
menu fills it in from the profile. The names were kept in two tables
with room for two of them for every bind of every user: 50,176 bytes
of pointers, nearly every one null, beside 25,088 bytes of binds.

Each user, and each port's controller, has a list of the binds that
have a name. It is as long as there are such binds - nothing for a
keyboard, a couple of dozen entries for a controller whose profile
names everything - and takes 512 bytes when there are none. The
build's uninitialised data falls by 49,664 bytes, and its code grows
by 1,952.

The tables were indexed by whoever wanted a name. They are asked for
now:
- input_config_bind_names() and input_autoconf_bind_names() give a
  bind's two names, both null for a bind with none, as an empty place
  in the tables read;
- ..._names_take() gives a bind names. The two parsers of a
  "..._btn_label" and "..._axis_label" still write into a label they
  are handed; their two callers hand them an empty one and pass on
  what comes back, so that a bind the file does not name gets no
  entry;
- input_config_bind_names_drop() and
  input_autoconf_bind_names_free() are what freeing the strings in
  place was: after a profile is saved from the menu, and when a
  controller's profile is cleared.

No change to what is shown, loaded or saved. The frame does not
read names: its input work over 300 frames (X11) is the same to the
instruction, 3,556,571 and 4,215,974.

The frontend harness gains a lane that holds the lists to what the
tables did - a bind with no name reads as two nulls; a name given is
the name read, replaces the one before and leaves the other; every
bind of a controller can have both; one bind's go, a controller's
all go - and goes through the two places names come from: a profile
read for a port, and the configuration's binds.

Checked besides: every other lane passes; the X11 keyboard session
logs the same as before, and the hotkey and quit-combination sessions
what master's binary logged. The Qt interface's one use is changed
the same way as the menu's two and was not compiled here.

The first part of the mapping compiler's fourth step (plan, section
21): the stored form as lists of what is set. The binds themselves
are next.
2026-10-08 07:34:53 +02:00
LibretroAdmin 8260c615aa rrar: the PPMd model uses the stdint types
rrar_ppmd7.h no longer declares Byte, UInt16, Int32, UInt32, Bool, True
and False; the model and its users take uint8_t, uint16_t, int32_t,
uint32_t and bool. In a unity build that also includes an Apple SDK
header, MacTypes.h declares UInt32 as unsigned long, which clashed with
the uint32_t typedef here on the legacy iOS lane.
2026-10-08 07:08:38 +02:00
LibretroAdmin c3a3457130 archive: RAR archives open and load like ZIP and 7z
A .rar file is an archive to the file browser (Open Archive / Load
Archive), to playlists, scans and content loading, and "x.rar#member"
names a member in it. The RAR backend reads through rrar_archive and
lists every member it can unpack alone; solid, encrypted and older-packer
members are passed over. HAVE_RRAR is on by default in Makefile.common
(--disable-rrar to drop it) and in griffin.

rrar_archive's bit reader helpers carry its prefix, so it builds in one
unit with rac3_decode.c, and the PPMd escape path keeps its 256 state
pointers in the model rather than on the stack (Ppmd7_DecodeSymbol's
frame goes from 2384 to 336 bytes, within the stack budget).
libretro-common/samples/file/archive_rar reads the reader's archives
through the archive layer.
2026-10-08 06:39:43 +02:00
LibretroAdmin db7628f49f config: putting the settings back to their defaults does not forget the connected controllers
config_set_defaults() reset the input's configuration with one call
that did two things: put every bind back to its default, and forgot
every connected controller - its names, its numbers, what its
profile gave. The second is no setting. It suited a start, and the
menu's reset to defaults, each of which is followed by the joypad
driver starting and finding the controllers again. It did not suit
unloading an override, which puts the settings back to their defaults
before reading the configuration again under a running driver: so
that path copied every profile's binds aside, with a strdup() of each
of their labels and the devices' names and numbers, called
config_set_defaults(), and copied it all back.

The two things are two calls (input_config_reset_binds(),
input_config_forget_controllers()). config_set_defaults() makes the
first; config_load() and the menu's reset make the second as well, as
before. Unloading an override forgets nothing and copies nothing; the
backup, its restore and the two helpers that copied the devices'
names out and back are gone - about a hundred lines.

Built in the Linux build; samples/config/defaults_target passes with
its stubs renamed. The frontend harness passes every lane and the
sessions log as before, but none of them unloads an override or
resets to defaults from the menu: that the two callers behave as
they did is by reading, not by a run.
2026-10-08 06:20:46 +02:00
LibretroAdmin 8ad8c6610a input: what the frame worked out from the remap table is kept until a remap changes
Two things were worked out from the users' remap table every frame.
Whether a user has anything remapped at all, which decides whether
the poll's remap work is done for them: a compare of the user's row
with the row that maps nothing, for each user at each poll - the one
library memory call the frame's input work made, nine times a frame.
And which of the RetroPad's sixteen a user has left as themselves: a
walk of sixteen at every read of the pad as a mask.

Both are kept (input_remap_kept) and made again at the first asking
after a change that is counted, with the rest of what is kept from
the binds. For that a remap has to be counted when it is made. The
defaults and a remap file's load already were. The menu wrote remaps
into the settings directly, in nine places, relying on every frame
with the menu open being counted; they go through
input_config_set_remap_id(), which writes and counts, and
tools/input_state_grab_check.py holds everything outside the input's
files and the configuration's loader to that (a selftest case with
it).

Measured, the frame's input work over 300 frames (X11):
- no controller: 3,652,253 instructions before, 3,556,571 after;
- a controller with a hotkey enabler: 4,310,052 before, 4,215,974
  after.

The frontend harness's lanes wrote remaps into the settings as the
menu did, in thirty-one places; they use the call. Its lanes on
remapping - a button remapped to another, to nothing, to an axis,
two to one, and back - are what hold this to "a remap made is the
remap read", and all pass.

Checked besides: the X11 keyboard session logs the same as before;
the hotkey and quit-combination sessions log what master's binary
logged.
2026-10-08 06:20:42 +02:00
LibretroAdmin 1df1eabad7 openxr: the loader lives in deps/openxr and holds no lock
The vendored loader moves to deps/openxr without the hex and handle
helpers (handles cast through XR_HANDLE_TO_U64/XR_U64_TO_HANDLE and print
with snprintf and PRIx64 from retro_common_api.h), platform_exports.h,
the CMake common_config and GLAD branches, and the CMake header list.

Its runtime, active instance, recorder table, messenger table and
property set are published by pointer and replaced by compare-exchange.
Creating, destroying and reinitializing take a one-word instance claim
and are refused, never made to wait, while another call holds it; that
also keeps xrInitializeLoaderKHR from unloading the runtime under a
create. An unloaded runtime is retired and freed once no enumeration can
still hold it. samples/openxr/loader_threads drives all of it from
several threads against a stand-in runtime, under TSan and ASan.
2026-10-08 06:02:03 +02:00
Davey HughesandLibretroAdmin 09ef8ed06f OpenXR: fix the double present transition after a readback, and the desktop CI job (#19734)
* vulkan: a frame that was read back is not sent to present twice

The OpenXR check placed between the readback block and its else took
the else for itself. In any build with OpenXR, a frame that read back
the backbuffer (a GPU screenshot or recording) was transitioned to
PRESENT_SRC a second time, from a layout it no longer had
(VUID-VkImageMemoryBarrier-oldLayout-01197).

* CI: the desktop OpenXR job enables OpenXR

OpenXR is opt-in since 9fb1d0edd5, so the job's configure built without
it and its check for HAVE_OPENXR stopped the job.

* vulkan: an OpenXR image that was read back goes back as an attachment

With a headset, a frame read back for a GPU screenshot leaves the XR
swapchain image in TRANSFER_SRC. The runtime takes its images back in
COLOR_ATTACHMENT_OPTIMAL, so that is where the image goes after the
copy, not to present.

The threaded-video harness reads back the viewport on Vulkan,
unthreaded and through the wrapper, under the validation layer, and CI
runs the harness once more on a tree configured with OpenXR.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-08 05:33:42 +02:00
LibretroAdmin 963cedc58e libretro-common: rrar_archive reads RAR 5
The reader takes the RAR 5 container besides the RAR 1.5 to 4.x one:
its headers (variable-length numbers, a CRC-32 on each), stored members,
and members packed with any of the five methods - blocks with a header
of their own, four Huffman codes, lengths and distances made up from
their codes, and the four filters (delta, x86 E8 and E8/E9, ARM).
Names are UTF-8 as they are.

It shares what RAR 2.9 already had here: the bit reader, the Huffman
decoder, the copying of matches, the member unpacked whole into the
buffer it is returned in, and filters run once it is all there.

Said to be unsupported, per entry or at open: solid members (the first
of a solid archive stands alone and is read), encrypted members and
encrypted headers, volumes, members whose size the header does not give,
and the RAR 7 format for dictionaries over 4 GB.

A RAR 5 archive can carry BLAKE2 hashes in place of CRC-32s. There is no
BLAKE2 here: such a member is unpacked and not checked, and the entry's
new has_crc field says so.

The data format follows libarchive's archive_read_support_format_rar5.c
(Grzegorz Antoniak, 2-clause BSD; its notice is in the file).

Tested. The sample test has five of libarchive's RAR 5 archives more -
stored, packed, an ARM program with delta and ARM filters, a solid one,
one with BLAKE2 only - checked for size, CRC-32 and what is refused, and
mangled 300 times each under ASan and UBSan like the others. Beyond
that:

  - all 65 of libarchive's RAR 5 test archives, most of them malformed
    on purpose, go through under the sanitizers without a fault; of the
    37 that unrar accepts, every member comes out but for two with no
    checksum, where the tables are bad and this reader says so;
  - archives made for the purpose with RAR 7.12 come out byte for byte:
    a 176 MB arcade image (4183 delta filters), 160 MB of x86 programs
    and libraries with a 64 MB dictionary (1818 delta, 186 E8 and 509
    E8/E9 filters), 58 MB of source text, at -m1, -m3 and -m5.

Speed, each decoder unpacking and checking the archive (seconds, best
of two, one core of a cloud machine):

                      176 MB image   160 MB of x86   58 MB of text
  rrar_archive            0.87           0.53            0.09
  unrar 7.00              0.98           0.61            0.10
  libarchive 3.7.2        1.35           0.84            0.14
  The Unarchiver          2.09           1.37            0.28
  dmc_unrar               2.21           1.54            0.32

Not in the committed test: RAR 5's x86 filters, which RAR only applies
to programs larger than a fixture should be.
2026-10-08 05:28:23 +02:00
LibretroAdmin 415d6c85fd config: saving does not reset the input in use, nor copy it aside to survive that
Saving the configuration with "save only what differs from the
defaults" - which is the default - has to know the defaults. It
filled a spare settings structure with them by calling
config_set_defaults() on it. But that call does more than fill the
structure it is handed: it resets the input's own state, whatever
the structure. So every save of the configuration, in the middle of
a game or not:

- forgot the active remap file and put every button, axis and key
  of the remap in use back to its default;
- put each port's kind of device and its analog-to-digital setting
  back (the lane below saw both at their defaults afterwards);
- reset every bind and forgot every connected controller's profile -
  which it had thought of, so it first copied all the binds, all
  their labels, all the profiles' binds and a strdup() of each of
  their labels aside, and afterwards copied them all back. Putting
  the binds back counted as a change to every bind, so everything
  kept from them was made again after each save.

Found by a lane in the frontend harness, run on master: a port's
kind of device, a remapped button and a stick's analog-to-digital
setting, set and then saved, were all three back at their defaults
after the save.

config_set_defaults() resets the input's state only for the settings
in use. For any other structure it writes the remap's defaults into
that structure (input_remapping_defaults_into(), the part of
input_remapping_set_defaults() that only writes settings) and sets
the ports' kind of device in it, and touches nothing in use.

The save then has nothing to put back, and copies nothing: not the
binds, their labels, the profiles or the profiles' labels. The copy
of the binds with the defaults loaded goes too: a bind's default is
made from its definition where it is compared, as a reset makes it.

What this does not look at: config_set_defaults() also sets the
audio gains, the video thread flag, the save and state directories,
the patch preferences and the recording directories of the program
in use from whatever structure it is filling. They are outside the
input's files, and are as they were.

The frame is not touched: 3,652,253 and 4,310,052 instructions over
300 frames (X11), against 3,652,209 and 4,310,063.

Checked: the harness's lane now holds a save to all of it - the
changed bind written and a default one not; the binds, a port's
device, a remap and a stick setting what they were afterwards; and no
bind counted as changed - and passes; so does every other lane.
samples/config/defaults_target passes with the new call stubbed
beside the others. The X11 keyboard session logs the same as before;
the hotkey and quit-combination sessions log what master's binary
logged.
2026-10-08 05:26:49 +02:00
LibretroAdmin cefea71724 libretro-common: the RAR reader is in libretro-common, with its test among the samples
708513c brought rrar_archive in from the flycast core's tree with that
tree's paths: the reader under core/libretro-common/, where nothing here
looks, and its test and archives under tools/archive/. They are moved to
where their kind lives:

  libretro-common/formats/rar/rrar_archive.c, rrar_ppmd7.c, rrar_ppmd7.h
  libretro-common/include/rar/rrar_archive.h
  libretro-common/samples/formats/rrar/rrar_archive_test.c, with a
    Makefile like its neighbours' and its four archives in archives/

The test runs without an argument now, on the archives next to it, and
is on the samples workflow's list of tests to run: it builds with ASan
and UBSan there and takes about four seconds.

The reader is also brought up to what the flycast core has, which is
faster. On a 176 MB arcade image packed with RAR 2.9 "best" (6156 LZ
blocks, 238 PPMd blocks, about 5000 filters), each decoder unpacking and
checking it (seconds, best of two, one core of a cloud machine):

  rrar_archive          1.86   (2.61 as it was in 708513c)
  unrar 7.00            2.21
  The Unarchiver (lsar) 3.18
  dmc_unrar             3.28
  libarchive 3.7.2      does not read it (no filters inside PPMd blocks)

What changed in it: the LZ loop keeps where it reads and writes in local
variables (a byte stored through the state's pointer made the compiler
read the state again for every symbol); the bit buffer is 64 bits wide
and takes four bytes at a time; a match is copied eight bytes at a time;
and the PPMd model calls RAR's range decoder by name, not through the
SDK's table of functions. The test's mangled archives caught one mistake
on the way, in the copy of a match that reaches back before the start of
a member, which is as it should be in what is here.

Nothing in RetroArch uses the reader yet: it is not in Makefile.common
or griffin, and archive_file has no rar backend.
2026-10-08 05:11:10 +02:00
LibretroAdmin 708513c69f libretro-common: rrar_archive, a RAR reader
A reader for RAR archives in the shape of rzip_archive and r7z_archive:
rrar_archive_open() over the whole file in memory, the entries, and
rrar_archive_extract() to a malloc'd buffer, checked against the
archive's CRC-32.

What it reads: the RAR 1.5 to 4.x container, stored members, and
members packed by RAR 2.9 to 4.x with any of the five methods - the LZ
coder and its Huffman tables, PPMd blocks, and the standard filters
(delta, x86 E8 and E8/E9, RGB, audio). Names come out as UTF-8,
including the packed UTF-16 form.

What it does not, and says so (RRAR_ERROR_UNSUPPORTED, or an entry's
"supported" at 0): the RAR 5 container, members packed by RAR 1.5 or
2.x, solid archives, encryption, multi-volume and self-extracting
archives, and filter programs other than the standard ones - there is no
RAR virtual machine in it.

Where it comes from. The data format - block headers, code tables, the
LZ symbols, filters and how the standard ones are told and what they do
- follows libarchive's archive_read_support_format_rar.c (Tim Kientzle,
Andres Mejia; 2-clause BSD, its notice is in rrar_archive.c). The PPMd
model and RAR's range decoder (rrar_ppmd7.c) are Igor Pavlov's
public-domain Ppmd7 as libarchive carries it, decoder only, left in its
own style. Nothing is taken from unrar or from GPL code.

How it is put together is its own. libarchive unpacks through a sliding
window and runs each filter as its block goes by. Here a member is
unpacked whole into the buffer it is returned in, and that buffer is the
window: a match is a copy from earlier in it. Filters are noted as they
are met and run when the member is all there, since they change bytes
that later matches still copy from as they were. Huffman codes are
decoded from a 10-bit table with a short search behind it.

Both files are C89 and build with -pedantic -Wall -Wextra.

Tested (tools/archive/rar_test.c, with four of libarchive's test
archives in tools/archive/rar; the next patch runs it from
tools/archive/run.sh): members with LZ blocks, with PPMd blocks, with
delta and E8/E9 filters, a stored one, directories and Japanese names
come out with the size, CRC-32 and name they are known to have; and 1200
copies of the archives, cut short or with bits changed, are opened and
unpacked under ASan and UBSan without a fault. Beyond those: the other
valid archives of libarchive's RAR tests (a 241 MB member that goes
between PPMd and LZ, 20 MB of many LZ blocks, random data) unpack to
their CRC-32, its malformed ones are refused, and a 176 MB arcade image
packed with "best" - 6156 LZ blocks, 238 PPMd blocks, 4204 delta, 747
audio and 2 RGB filters - comes out byte for byte as The Unarchiver
makes it, in 2.8 s.

Not exercised by any archive at hand: the plain E8 filter (E8/E9 is).
2026-10-08 05:00:13 +02:00
LibretroAdmin 23a95e5ee6 menu: a bind is asked for by its number, not found by stepping along a user's row
Four places in the menu took a pointer to the first of a user's
binds and reached the others by adding to it or stepping it: the two
that start a capture of every bind, the loop that clears a user's pad
binds after a profile is saved, and the loop that puts a user's binds
back to their defaults. All four rest on a user's binds being a row.

Each asks for the bind it wants by user and number. The pointers are
the same ones.

The step to the next bind in the keyboard's own capture
(USE_CUSTOM_BIND_KEYBOARD_CB) asks for it by the capture's user, and
"bind all" now records whose binds it is for, which only the single
bind did. Nothing in the tree defines that option, so it is compiled
by nobody; it compiles with the option given by hand.

Built in the Linux build and the Android unity build. The frontend
harness passes every lane and the sessions log as before; none of
them binds a key through the menu, so that the four give the same
pointers is by reading, not by a run.

Groundwork for the mapping compiler's fourth step (plan, section
21).
2026-10-08 04:58:48 +02:00
LibretroAdmin d3fc95e591 input: a copy of the binds is the input file's to make, index and put back
The configuration keeps copies of the configured binds in three
places: before it loads the defaults over them to learn what the
defaults are, the defaults themselves to save only what differs, and
before an override file is loaded over them. Each was a table of the
binds' own size and shape that the configuration allocated, had
filled by a memcpy in the input header, and indexed by user and bind
for itself.

A copy is an opaque thing now (input_config_binds_copy_t), with the
five things that are done to one: made of the binds as they are,
taken again, asked for a user's bind, put back, freed. The
configuration no longer knows what the binds are stored as; a copy
is the table over again, for now, and can stop being one.

No change to what is saved or loaded. The frame is not touched:
measured over 300 frames (X11), 3,652,209 instructions with no
controller and 4,310,063 with one, against 3,652,216 and 4,310,052.

The frontend harness gains a lane that saves the configuration in
"only what differs from the defaults" mode with one bind changed:
that bind is in what is written, a default one is not, and the binds
are afterwards what they were. Its lane on the change count goes by
the new calls: putting a copy back is counted as a change, taking
one is not.

Checked besides: the harness passes every lane; the X11 keyboard
session logs the same as before, and the hotkey and quit-combination
sessions what master's binary logged.

Groundwork for the mapping compiler's fourth step (plan, section
21).
2026-10-08 04:58:44 +02:00
LibretroAdmin a3b7f40920 input: the configured binds are read from their table, not through sixteen pointers to it
The frontend's input state kept a pointer for each of sixteen users
to that user's row of configured binds (libretro_input_binds). Each
was set once, at start, to the row of the one table there is, and
never to anything else. But the frame's readers went through them:
to learn whether a button's bind is usable, a read loaded the port's
pointer, tested it for null, and only then looked at the row it had
always pointed at - and the same for the hold and turbo rules at the
poll, the sticks' binds, and the table handed down the state call.

They read the table. The pointers and the null tests go, and the
member goes from the state structure. Before the binds are set up
the table is all noughts, which reads as "not usable", as the null
pointer did.

Measured, the frame's input work over 300 frames (X11):
- no controller: 3,683,159 instructions before, 3,652,216 after;
- a controller with a hotkey enabler: 4,341,888 before, 4,310,052
  after.
The poll and the core's read path fall by about 20,000 and 16,000;
one function further along the structure rises by 4,500, its fields
having moved up by the member's 128 bytes.

Checked: the frontend harness passes every lane; the X11 keyboard
session logs the same as before; the hotkey and quit-combination
sessions log what master's binary logged.

About the second half of the mapping compiler's third step (plan,
section 21), which was to take the other fifteen users' hotkey rows
out of the table: that is not a deletion. The menu takes a pointer to
a user's row and walks it, the configuration copies whole tables to
and fro, and the row's type is the one the drivers' interface and a
controller's profile share. Those are the same things the fourth
step has to change, when the stored form becomes lists and the rows
go altogether; the 7,080 bytes go with it then. This is the first of
that groundwork: one less way of reaching a row.
2026-10-08 04:37:48 +02:00
LibretroAdmin 8b8fc1ea78 input: the hotkeys are the first user's, on whichever port they are read
There is one set of hotkeys in every way but one. The configuration
loads and saves the first user's and no other; the menu shows only
those; the keyboard's events, the menu's keys and the quit
combination go by the first user's. But the tables have a row of
hotkeys for each of sixteen users, and the hotkey pass read the row
of the port it was on. That port is the first unless "Hotkeys Follow
Player 1" is on and that player is mapped from another - and then it
read a row nothing fills: a hotkey on a key of the keyboard did
nothing there, and of a controller's only the ones its profile gives.

INPUT_CONFIG_BIND(port, i) says what a port's bind is, in one place:
- for the RetroPad, the sticks, the lightgun and the rest that are a
  port's, the port's own;
- for a hotkey, on the port the hotkeys are read on, the first
  user's;
- for a hotkey on any other port, none of the port's - which is what
  those rows held - so that only its controller's profile can be
  behind one, as before.
What is kept and made from the binds goes through it: a port's keys,
its resolved pads, its hotkey facts, the hotkeys' set, and the slow
path's read for a controller that is not the port's own.

The hotkeys' port is worked out once a frame, where it was worked
out again at each of the ports, and a move of it is counted as a
change to the binds, since it changes what a hotkey's bind is for
two of them.

With the hotkeys on the first port, which is the default, the binds
read are the ones that were. Measured, the frame's input work over
300 frames (X11): 3,690,476 instructions to 3,683,159 with no
controller, 4,354,646 to 4,341,888 with one - the port worked out
once.

A first version made a hotkey's bind the first user's on every port.
Every port's keys then listed the first user's hotkey keys, and the
driver was asked for them sixteen times a poll: 4,460,422 with no
controller. It would also have made the first user's own "enable
hotkeys" button count on every controller.

The frontend harness gains a lane: a keyboard with the pause
hotkey's key down pauses with the hotkeys on the first port, and with
them on the second. Run on master's code the second fails - "the
pause key did not pause with the hotkeys on the second port" - and
here it passes.

Checked besides: the harness passes every lane; the X11 keyboard
session logs the same as before; the hotkey and quit-combination
sessions log what master's binary logged.

The first half of the mapping compiler's third step (plan, section
21): nothing reads a hotkey from another user's row any more. The
rows themselves - 7,080 bytes of the configured table - go next.
2026-10-08 04:22:19 +02:00
LibretroAdmin 2c20e986d6 webos: the on-screen keyboard is up while a line is typed, not whenever keys are kept from the binds
The webOS code in the SDL2 and SDL3 input drivers and in the Wayland
webOS file asks input_driver_keyboard_mapping_blocked() in five
places, each time to learn whether the on-screen keyboard is up: to
let the remote's arrows and OK move about its grid instead of being
keys, and to leave the remote's special keys alone meanwhile.

"Keys kept from the binds" is true while a line is being typed, and
also under Game Focus and while the menu waits for the key to bind.
In those two there is no on-screen keyboard: with Game Focus on, the
arrows and Enter of a keyboard were taken for moves about a grid that
is not there and did not reach the core, and the menu could not be
given one of them as the key to bind.

The five ask input_driver_text_entry_active(), which is the question
they mean: a line is being typed.

Not compiled and not run: the webOS code needs its SDK, and a TV. The
two calls are declared side by side and have the same shape; the SDL2
driver compiles without its webOS code. What this does on a TV -
that the on-screen keyboard behaves as it did while typing, and that
Game Focus and binding a key no longer behave as if it were up -
wants looking at there.
2026-10-08 04:22:15 +02:00
LibretroAdmin e48144d32b CI: MSYS2 packages are installed in a step that retries
setup-msys2 installs its package list in one pacman transaction and
fails the job when a single mirror stalls on a single file. The
MSYS2, Windows VFS and d3d harness jobs now set up MSYS2 bare and
install the same packages with pacman in a step of their own, up to
three times; the packages already fetched stay in the cache, so a
retry fetches only what the stalled mirror did not deliver.
2026-10-08 04:18:37 +02:00
LibretroAdmin 87cfa110a1 input: an input driver's state call is not told the keyboard is held
Every input driver's state call took a flag, keyboard_mapping_blocked:
whether the keys are kept from the binds. It mattered when drivers
answered the binds themselves. The frontend answers them now, from the
keys it asks a driver for once a poll, and applies the flag itself; a
driver's state call answers the keyboard, the mouse and the pointer,
and none of the twenty-three read the flag. (udev passed it to its
touch code, which did not read it either.)

The parameter goes: from the interface, the twenty-three drivers, the
frontend's own stand-in driver and its nine calls. The frontend's own
functions keep theirs - that is where it is used.

Measured against this same master built without it, the frame's input
work over 300 frames (X11): 3,690,770 instructions to 3,690,476 with
no controller, 4,354,940 to 4,354,646 with one. An argument a call.

Checked: the frontend harness passes every lane; the X11 keyboard
session logs the same as before, and the hotkey and quit-combination
sessions what master's binary logged; the raw input keyboards test
and the plain Win32 input test, which call a driver's state function
themselves, pass under Wine. Built: Linux in full; DirectInput, raw
input and the plain Win32 driver with a mingw compiler; the Android
unity build with the NDK; Wayland; the Visual C++ 6 project and the
QNX driver on their stand-ins; a build with the optional features
off. Not compiled, there being no compiler for them here: Cocoa, UWP,
the web driver, SDL 1 and the console drivers (DOS, Wii, PS3, PS4,
PSP and Vita, Switch, Wii U); theirs is the same one line, taken out
by script.

The second of three pieces before steps 3 and 4 of the mapping
compiler (plan, section 21). The joypad, its info and the binds are
handed to the same call, and two drivers use any of it: the Wii's
reads one field of the info, the Wii U's asks the joypad for its
touch screen. With those two reworked the call would take the port
and what is asked, and nothing else.
2026-10-08 03:29:08 +02:00
LibretroAdmin 74042b66ed apple/fastlane: a nightly held back during a review is called that
The upload that skips the tester groups while another build is in
TestFlight review is a nightly the testers get at the next run after
the review, not an internal build; the comment and messages say so.
2026-10-08 03:12:42 +02:00
LibretroAdmin 18655a624a apple/fastlane: an internal TestFlight upload is not sent for beta review
pilot submits a build for beta review whenever groups are given, and
an empty list counts, so the upload meant to skip distribution while
another build is in review submitted anyway and was refused. The
internal upload now passes no groups and asks for no review. A build
that enters review between the check and the end of processing still
refuses the submission; that is reported and the job passes, since
the upload has landed by then.
2026-10-08 03:09:34 +02:00
LibretroAdmin 8646a73bb2 CI: the archive fallback keeps apt's package lists
When a package download stalls, the install steps switch to
archive.ubuntu.com and refresh. That refresh now treats any index it
could not fetch as a failure, is retried without translations or
command-not-found data, and puts the runner's own mirror back when
the archive will not answer either, so the next download attempt
always has package lists to install from.
2026-10-08 03:06:51 +02:00
github-actions 5475d2bb6d Fetch translations from Crowdin 2026-10-08 00:26:49 +00:00
LibretroAdmin 0c1e10fcd1 input: the keyboard has holders, as the pointer has
Game Focus keeps the keyboard's keys from the binds and the hotkeys;
so does a line of text being typed, and so does the menu while it
waits for the key to bind. All three did it with one flag
(INP_FLAG_KB_MAPPING_BLOCKED), which each set and each cleared, as did
the end of a line from a platform's keyboard and a driver restart.
Whichever ended first cleared it for the others: a line typed and
ended with Game Focus on gave the keys back to the binds with Game
Focus still on, and Game Focus turned off in the middle of a line
gave them back in the middle of it.

That is what the pointer's capture was before it was given holders,
and the keyboard gets the same: input_keyboard_capture_hold() and
_release(), with a name for each - Game Focus, text, the wait for a
bind. The flag is set while anyone holds. Every reader of the flag is
as it was, and costs what it did.

- Game Focus holds and releases under its own name. Turned off, it
  gives the hotkeys back only if nobody else has the keyboard.
- Text entry holds when a line is opened and releases when it is
  completed or ended.
- input_driver_set_keyboard_mapping_blocked(), which only the menu's
  wait for a key calls, holds and releases under that name; a key
  arriving for a deferred wait releases it too.
- A driver restart releases all three, as it cleared the flag before;
  Game Focus takes its hold again when it is reapplied, as before.

input_driver_text_entry_active() is new: whether a line is being
typed, as distinct from the keys being kept from the binds for any
reason. Nothing calls it yet. The webOS code in the SDL drivers and
the Wayland webOS file asks input_driver_keyboard_mapping_blocked()
where it seems to mean "the on-screen keyboard is up", which is also
true under Game Focus and while the menu waits for a key; they are
left as they are, that being something to settle on a TV.

Measured, the frame's input work over 300 frames (X11): 3,688,254
instructions with no controller and 4,333,679 with one, before and
after - the same to the instruction.

The frontend harness gains a lane: each of the three holds the
keyboard, a line ended under Game Focus leaves the keys kept, Game
Focus going off under a line leaves them kept, a line ended during
the wait for a key leaves them kept, and each lets go when it is
alone; and neither Game Focus nor the wait reads as a line being
typed. The two "leaves them kept" under Game Focus are what the
single flag got wrong.

Checked besides: the harness passes every lane; the X11 keyboard
session logs the same as before; the hotkey session and the
quit-combination session log what master's binary logged.

The first of three pieces, before steps 3 and 4 of the mapping
compiler (plan, section 21). Next: the keyboard_mapping_blocked
parameter out of every input driver's state call, where nothing
reads it any more; then the masks made from the keys cleared once at
the poll while the keyboard is held, in place of a test at each read.
2026-10-07 20:45:26 +02:00
libretroadmin 9fb1d0edd5 HAVE_OPENXR should be manually enabled for now 2026-10-07 20:41:18 +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 4a8499d302 Core updater harness: the scan's gather bound holds the window open
The per-frame I/O window is a time budget, so how many list entries
one gather covers depends on how fast the machine runs. The bound on
gathers is checked in its own unthreaded pass with the window held
open, where one handler call must take the whole list and a handler
that does one item per call fails at any speed. The timed pass keeps
the stat and core info checks.
2026-10-07 20:39:11 +02:00
LibretroAdmin 617ccd0c58 input: a bind change marks a port's resolved pads and hotkey facts stale itself
A port's resolved pads and its hotkey facts are made from the binds
and kept until a bind changes. Each read of either found that out by
loading the binds' change count and comparing it with the count the
thing was made at - for the hotkey facts, sixteen ports a frame; for
the pads, wherever a controller's pad may be behind a bind.

A change to a bind says so to them directly instead.
input_config_binds_changed() calls input_binds_kept_invalidate(),
which noughts the count each port's pads and hotkey facts were made
at; a read looks at that one field, in the structure it is about to
hand out, and makes it again where it is nought. A change made on
another thread while one is being made is caught by looking at the
count again afterwards. Which controller's profile a port is read by
is still compared at each read: that is a setting, and a change to
it is not always counted with the binds.

It also takes over what the last commit did for the keys from the
header: the count of ports whose keys the poll made current is
noughted in the same call, and is private to the frontend's file
again.

Measured, the frame's input work over 300 frames (X11):
- no controller: 3,694,137 instructions before, 3,688,254 after;
- a controller with a hotkey enabler: 4,362,696 before, 4,333,679
  after.
What a change costs is thirty-two stores, where every read of any of
it compared a count.

Tried first: a bit a port in one word, tested by the read. With the
port a variable, shifting for the bit cost as much as the compare it
replaced - the count with no controller rose by 4,800.

Checked: with the two noughts taken out of the new call, the frontend
harness fails in the poll-mode and first-press lanes and others,
which is what holds this to "a changed bind is the bind that is
read"; as written it passes every lane. The X11 keyboard session
logs the same as before; the hotkey and quit-combination sessions
log what master's binary logged.

Of the mapping compiler's fifth step (plan, section 21).
2026-10-07 20:01:03 +02:00
LibretroAdmin 3055ac3c27 CI: samples/audio installs everything in its bounded install step
ffmpeg, pulseaudio, libpulse-dev, the MinGW compiler and wine64 are
fetched with the job's other packages, under the download timeout
that moves a stalled mirror over to archive.ubuntu.com. The test
steps no longer run apt themselves, so a mirror that stops answering
mid-job fails over instead of holding a step until the job is
cancelled.
2026-10-07 19:52:00 +02:00
LibretroAdmin 09907606e2 input: a port's keys are made current at the poll, not asked after at each read
What is kept of a port's keys - which binds name a key, which of them
are down, the masks made from that - is read some fifty times a
frame, and each read first asked whether it was still current: the
binds' change count against the one it was made at, and the poll's
against the one its keys are from. Two loads and two compares, to
learn each time that nothing had changed since the poll.

The poll makes the keys of every port in use current itself, once
(input_port_keys_at_poll()), straight after the driver's own poll,
and notes how many ports that was. A read of one of those takes what
is kept and asks nothing. The driver is asked for each port's keys as
often as it was - once a poll - only at the poll instead of at the
port's first read.

A bind changing is what makes them not current, and it takes the
count of ports back to none (input_config_binds_changed() does it,
as does the poll of the devices alone that a driver restart does):
from then until the next poll a read asks, as every read did. So a
read never goes by binds older than the ones there are. A change
made on another thread while the poll is at it is caught by looking
at the count again before the ports are noted.

An input driver that gives no list of keys keeps none, as before.

Measured, the frame's input work over 300 frames (X11):
- no controller: 3,745,702 instructions before, 3,694,137 after;
- a controller with a hotkey enabler: 4,418,630 before, 4,362,696
  after.

Tried first and not here: the pressure-sensitive buttons' reader
taking what is behind a button from what is kept instead of from the
bind's record. For one bind, finding out that what is kept is current
costs more than reading the record - the count rose by 12 a frame
with no controller and 34 with one. That is what pointed at the
asking itself.

The frontend harness gains a lane for what this could get wrong.
With a keyboard that has one key down, the RetroPad's B on that key
reads; the bind moved to another key after a poll, the read that
follows does not, and moved back it does; the key let go is seen at
the next poll. With the read made to go by a count that only the
poll sets, the lane fails at both moves. (The bind is moved before
the frame's first read: a port's pad is put together for the core
once a frame, so a read later in the frame would be given the same
answer on any version.)

Checked besides: the X11 keyboard session logs the same as before;
the hotkey and quit-combination sessions log what master's binary
logged; the harness passes every lane.

Of the mapping compiler's fifth step (plan, section 21): the first
of what is kept to be made current when it changes instead of
checked when it is read. The port's resolved pads and the hotkey
facts are checked at each read in the same way.
2026-10-07 19:35:26 +02:00
libretroadmin 13d14ff50d gx, gekko: textures, and overlay packs shown through them
Both drivers load, update and unload textures: a texture is its
texels tiled as GX RGBA8 into memory of its own, which the GPU samples
where it lies. An update tiles the new texels over the old - the GPU
has finished drawing by the time a frame returns, so nothing reads
them meanwhile. image_texture_tile_gx_copy() does the tiling into a
separate buffer, padding a side that is not a multiple of 4 instead of
cutting it.

The overlay pack is shown as those textures (load_textures), so it is
tiled once, kept across the overlay cache and freed from the CPU, and
an animated overlay plays. A pack is no longer tiled as it decodes and
the surface requirements on GEKKO are the common ones; a page shown
through load() is tiled into the overlay's own memory. gx's overlay
filter is set on the overlay's texture, not the core frame's. CI
compiles gekko_gfx.c, and both drivers' overlay code.
2026-10-07 19:11:21 +02:00
LibretroAdmin 2a515ab854 input: the plain mask takes which binds are usable from the port's keys
A core that reads the RetroPad as a mask is answered in one go where
the port has no hold or turbo going: three masks for all sixteen
buttons. One of them is which of the sixteen have a usable bind, and
it was worked out by looking at sixteen binds' records, every port,
every frame.

The port's keys are rebuilt in one pass over its binds whenever a
bind changes, and that pass already tests each bind for being usable.
It keeps the sixteen's bits (usable16), and the plain mask reads them
after one comparison that says they are of the binds as they stand.
Where they are not - an input driver that gives no list of keys keeps
none - the records are looked at as before.

Measured, the frame's input work over 300 frames (X11):
- no controller: 3,786,758 instructions before, 3,745,702 after;
- a controller with a hotkey enabler: 4,458,394 before, 4,418,630
  after.

Two things tried on the way, and why they are not here:
- A table of usable bits for every bind of every port, for all eleven
  places that test a bind for it. A single test read from the table
  costs as much as the test of the record or more, and the table is
  1,536 binds to go through at each change. The single tests stay on
  the records until the stored form changes, when "usable" stops
  being a flag on a record at all.
- The new field among the structure's other masks. It moved every
  field after it, and the count rose by about 14,000 in the poll and
  more elsewhere - every reader of the rest paid an instruction or
  two an access. It is in what was padding at the end: the structure
  is the size it was and nothing in it has moved.

The frontend harness gains a lane that holds the RetroPad read as a
mask to the sixteen read one at a time, which go by the records: with
every bind usable, a button remapped to another, that button's bind
not usable as well, usable again, and the remap undone. Remapped and
not usable is where the bits decide what the core gets: the button's
own state reaches it again, which the lane also checks outright.

It runs twice. The harness's own input driver gives no list of keys,
so nothing is kept for it and the records are read; the second time a
driver that gives one stands in, and the kept bits are what is read.
With the kept bits made to say "usable" always, the lane fails at the
third step of the second run. (As first written it ran with the
harness's own driver only, where the new code is never reached, and
could not have caught anything.)

Checked besides: the X11 keyboard session logs the same as before;
the hotkey and quit-combination sessions log the same lines on
master's binary and this one; the harness passes every lane.

The eighth piece of the mapping compiler's fifth step (plan, section
21).
2026-10-07 18:59:13 +02:00
LibretroAdmin c0069d5c1b gfx: texture uploads refuse what failed instead of handing it out
A lent GL slot read as free once its upload's fence was gone, so a
fence that could not be made (glFenceSync returning 0) handed the
producer memory the GPU might still be reading. A slot now carries an
upload in flight apart from its fence: with none made, readiness makes
one when asked, which follows the upload in the command stream, and
the slot stays the GPU's until it signals.

The GL drivers' set of half-float texture names could fail to grow,
leaving a texture out of it to be drawn as SDR. Room is now made before
the texture, and a load with none is refused.

A Vulkan static texture whose upload could not be placed on the batch
came back with its image never written; it is destroyed and the load
refused. The upload batch's fallback fence is not submitted with when
it could not be reset, and its resources are released after the wait
only when the upload is known to have run or the device is lost.

The harness gains a lane that refuses GL fences around a lent upload,
and allocator fault injection for one that makes the half-float set
unable to grow.
2026-10-07 18:39:33 +02:00
libretroadmin ce6eaace22 font: the atlas's dirty region is sent as rectangles round its cells
A frame's new glyphs land in whichever cells are least used, far apart,
and the one rectangle round them all took up to eleven times the
pixels that changed - every one of them converted on the CPU by the
drivers that expand the atlas before uploading it. The atlas keeps the
region as up to sixteen rectangles as well, the cells as drawn, and
when they run out the two whose union adds least are joined;
dirty_xy0/xy1 stay their union for every other consumer.

gl1, gl2, sdl2 (both fonts), sdl3, d3d8, d3d9, gdi and ctr convert and
send each rectangle, and ctr flushes its texture once for all of them.
With 10 new glyphs a frame the pixels sent fall from 64,700 to 5,400,
with 40 from 100,600 to 31,600. font_frame_test checks the rectangles
hold every new cell and come to far fewer pixels than their union.
2026-10-07 18:28:57 +02:00
LibretroAdmin f843c73c57 openxr: nothing is held when the runtime has no fresh state to give
The XR frame loop syncs the controllers into atomic words the input
drivers read. Whenever a sync brought nothing new, the words kept
what the last good one had put there, and the readers went on being
given it:

- A sync that failed returned early. That includes the session
  without the focus - XR_SESSION_NOT_FOCUSED is not XR_SUCCESS - which
  is every frame the headset's own menu is up: a button down when it
  opened stayed down for the frontend until it closed.
- A stick or a trigger whose state could not be had kept its last
  value (a button's was cleared).
- A session that is not running is not synced at all, so the frame
  loop left the last state standing for as long as it was stopped.
- Deinit cleared the held menu button and nothing else: the last
  buttons and axes stayed readable with no session.

All four are "nothing held" now. openxr_input_release_all() clears
what is published; the sync calls it on anything but success, the
contexts' frame loops call it (openxr_input_idle()) on a frame they
do not begin, and deinit calls it. A control whose state cannot be
had, or that the runtime says is not active, reads as not held.
Readers are not made to ask whether a session is live on every
button: what they read is cleared where it stops being true.

The words are four where they were twenty: the buttons as a set, each
stick's x and y in one word, the two triggers in one. A stick's two
axes were stored and loaded apart, so a read could take x from one
sync and y from the next; a pair is one sync's. A sync stores four
words in place of twenty. One word is still not the next one's sync -
a button and a stick read in the same poll can be a sync apart.

samples/input/openxr_input builds the file as it ships, with the
host's compiler and the vendored headers, against a stand-in for the
runtime, and holds it to the above: held controls as reported, failed
queries, a failed sync, the session out of focus, not running, deinit,
and the held menu button through each. Against the file as it was it
fails seven of its checks, with the values an audit of master
reported (a stick at 24575 and a trigger at 16383 after their queries
fail; a button, the axes and the menu button after a failed sync and
after deinit). A workflow runs it.

Compiled besides: the Android unity build with HAVE_OPENXR, with the
NDK. Not run on a headset.
2026-10-07 18:02:11 +02:00
LibretroAdmin 5880a6abf2 frontend harness: the stick checks read the two slots there are
A port's read of a stick is kept in a slot of the stick's own, and
there are two: the left stick's and the right's. There were four when
two joypad drivers could each have a pad on a port, and the "sticks
read once" lane still went by that layout - it or-ed slots 0 to 3
together and looked for the left stick's read in slots 0 and 2. Slots
2 and 3 are past the end of the array. The lane passed, on whatever
was there.

It reads the two slots, and is held to more than it was: both sticks'
reads are kept, each in its own slot, x and y from the one read; a
read later in the frame is given the kept value, either axis of
either stick; the poll drops both; and after it a read goes to the
pad again.

Found by an audit of master, whose compiler warned of the bounds.
The product's own code indexes the two slots only (the slot is the
stick's index); this was the test alone.

The harness passes every lane with it.
2026-10-07 18:02:07 +02:00
LibretroAdmin 2b37507949 input: a joypad driver is handed what is behind the RetroPad, not the binds
A joypad driver's state call answers the RetroPad's sixteen as a mask.
It was handed the port's binds for it, and every driver chose, for
each of the sixteen at every call, between the port's own bind and
its controller's profile's: thirty-eight copies of the same two
lines, each a reason the binds' records cannot change shape.

The frontend has made that choice already and keeps it - the port's
resolved set, worked out when a bind changes. The state call takes
two lists from it instead: the pad's button behind each of the
sixteen (NO_BTN for none) and the pad's axis (AXIS_NONE for none).

   int16_t (*state)(rarch_joypad_info_t *joypad_info,
         const uint16_t *joykeys, const uint32_t *joyaxes, unsigned port);

and the same for an HID backend's. In the 29 joypad drivers, the Wii
U's three and the six HID backends that is the signature and the two
lines, by script: each keeps its own loop and whatever is its own
about it (the DOS one answers nothing, the PS4's and the parallel
port's go by buttons alone, and so on). No file of theirs names a
bind, a set of binds or a profile's any more, and
tools/input_state_grab_check.py holds them to it (a selftest case
with it).

The frontend's side:
- input_joypad_port_state() makes the call. For a port read by its
  own controller it hands over the resolved set's lists as they
  stand, and answers "none" itself where no pad is behind any of the
  sixteen. For a read by another controller it resolves the sixteen
  for that call, from the port's binds and that controller's profile.
- The resolved set writes the sixteen's places whatever is behind
  them, so the lists can be handed over whole.
- The frontend's own loop, which stands in for the drivers read
  through a snapshot, takes the same two lists; it found the port's
  set from the address of the binds, and finds it from the address
  of the list.
- Learning a trigger's rest asks whether the axis's other direction
  is bound to anything: of the port's resolved set, where there is
  one. For a read by another controller it sees the sixteen it was
  handed, where it used to see every bind of the port.

Measured, the frame's input work over 300 frames (X11):
- no controller: 3,796,163 instructions before, 3,786,758 after;
- a controller with a hotkey enabler, through a driver that is read
  directly: 4,478,984 before, 4,458,394 after. Its state call alone:
  190,376 to 147,272.

Checked:
- samples/input/joypad_bridge_checked, which holds six drivers'
  state calls (udev, linuxraw, SDL2, SDL3, DirectInput and the
  XInput-only one) to the frontend's loop from the binds, over
  thousands of random pads, binds and thresholds: all six pass with
  the lists.
- The X11 keyboard session logs the same as before; the hotkey and
  quit-combination sessions log the same lines on master's binary
  and this one; the frontend harness passes every lane; the
  multimedia joystick driver's test passes under Wine.
- Built: Linux in full, and the parallel port, SDL2, HID and libusb
  files compiled beside it; Windows in full with a mingw cross
  compiler (the XInput and DirectInput driver, the DirectInput one,
  raw input and the multimedia joystick among it), and run through
  the scripted session under Wine; the Android unity build with the
  NDK, with OpenXR on; the Visual C++ 6 project and the QNX drivers
  on their stand-ins; a build with the optional features off.
- Not compiled, there being no compiler for them here: the console
  drivers (3DS, DOS, GameCube and Wii, PS2, PS3, PS4, PSP and Vita,
  Switch, Wii U, Xbox, web), the Apple one, SDL 1, and the HID
  backends but libusb's. Their edits are the script's, the same in
  every file.

The seventh piece of the mapping compiler's fifth step (plan, section
21). Counted as before, binds are read from the tables at 107 places
in 26 functions of the frontend's file, from 113 in 28 - and in no
driver's.
2026-10-07 17:50:48 +02:00
libretroadmin 43798a27e2 libretro: the VR surface converges on a view map
The core-facing VR API becomes PR 19729's view map before anything
else ships against the quest numbers: SET_VIDEO_VIEWS (95) takes the
view array - eye-tagged rectangles of the core's frame - with the
quest request's remaining needs folded into retro_video_views as a
flag word (REQUEST_FLAT), the reference space, the IPD hint and the
recommended view size outputs, and GET_VIDEO_VIEWS_STATUS (96) answers
with PRESENTS/STEREO/HMD flags. SET_VR_CONTENT_INFO, its struct and
the layout enum are gone - stereo-native side-by-side is simply two
eye-tagged views - and GET_VR_FRAME_STATE and GET_VR_HEAD_POSE move
to 97 and 98 with their structs unchanged. All experimental numbers,
with the one consuming core switched in the same sweep.

Inside the frontend the old request shape survives only as the
internal video_vr_content_info_t fed by a view-map adapter that
accepts the one shape the headset path consumes today - two
full-height eyes side by side - and refuses anything else honestly
until 19729's consumer lands. Drivers gain an optional
get_video_views_status answering the flags for a live session,
wired for gl2 and vulkan; the slot sits at the guarded vtable tail,
so every other driver leaves it zero.
2026-10-07 17:45:40 +02:00
LibretroAdmin d62402d770 openxr: the controllers are read after the wait for the frame, not before
Both XR contexts began a frame by syncing the controllers and then
waiting for the frame (xrWaitFrame), which takes up whatever is left
of the frame's period. The frontend polls straight after the frame is
handed in. So what a poll read was taken before the wait: most of a
period old - up to about 14 ms at 72 Hz, less the time the core took.

The sync is after the wait now, in both contexts. What the next poll
reads is as old as the frame took to draw. Nothing else moves: the
same call, on the same thread, once a frame, and still made when the
wait fails.

This is instead of syncing at the frontend's poll, which the plan
(section 22) had as a question. That would have the frontend's thread
use the session while the video thread's loop runs it and takes it
down, and put the runtime's calls on the poll, to gain over this only
under threaded video, and there half a period on average.

Compiled: the Android unity build with HAVE_OPENXR, with the NDK.
Not run on a headset; the gain is worked out from the order of the
calls, not measured.
2026-10-07 17:19:47 +02:00
LibretroAdmin 3edf4ca3c0 input: the keys helper is not handed a port's binds
input_joypad_from_keys() answers for a port's RetroPad from the keys
and mouse buttons bound to it, out of what is kept for the port. It
still took the port's row of binds - its last use of it went with the
mouse buttons - and its four callers still found the row to pass.
The parameter goes, and with it three reads of the tables in the
state call's slow path and one in the frame's own read.

The one mouse-button bind that path still looked up in the record
(a bind past the RetroPad's sixteen with no pad behind it) is read
from what is kept, like the rest.

Measured, the frame's input work over 300 frames (X11):
- no controller: 3,800,502 instructions before, 3,796,163 after;
- a controller with a hotkey enabler: 4,483,319 before, 4,478,984
  after.

Checked: the X11 keyboard session logs the same as before; the
hotkey and quit-combination sessions log the same lines on the binary
before this and the one after; the frontend harness passes every
lane.

The sixth piece of the mapping compiler's fifth step (plan, section
21). Counted as before, binds are read from the tables at 113 places
in 28 functions of this file, from 118 in 29. Most of what is left
on the frame's path hands a port's binds to a joypad driver's state
call, which reads them: that is the next thing to change, and it is
an interface every joypad driver has.
2026-10-07 17:15:04 +02:00
LibretroAdmin 4d69e4f5a1 openxr: the menu button held is a state, not a flag taken on reading
The headset's menu button, held a second, opens the menu: the XR
frame loop sets a flag at each sync while the button has been down
that long, and the frontend's hotkey pass reads it as the menu hotkey
being down.

The read took the flag away (an atomic exchange with 0), and the next
sync put it back. That is one press only while the frontend reads
exactly once between two syncs. Read twice between them - a frame
that presents nothing, fast-forward, the video thread's loop running
at another rate than the frontend's - it saw the button let go and
pressed again, and the frontend's hotkeys act once a press: the menu
toggled again, for as long as the button was held. The two logics run
against the same three seconds of holding, a sync every 11 ms: one
read a sync, one toggle either way; two reads a sync, 179 toggles
before and one now; three reads to two syncs, 90 and one.

The flag is a state now: set from a second into the press until the
button comes up, by the sync, and looked at by the read - an atomic
load where there was an exchange, every frame.

The hotkey pass also asked for it only when the port was the first.
Its loop runs for the one port the hotkeys are read on, so with
"Hotkeys Follow Player 1" and that player on another port the held
button did nothing. It is asked on whichever port that is.

Compiled: the Android unity build with HAVE_OPENXR, with the NDK.
Not run on a headset.
2026-10-07 17:15:00 +02:00
LibretroAdmin 8f2fc749de input: the sticks are read from what is kept, not the binds' records
A stick's two readers - one axis, or both at once - took the records
of the stick's four binds each read: six times whether a bind is
usable; each one's key, and for each key a read of the keyboard
through the whole of the driver's state call; and for each pad axis
and button the choice between the port's own bind and its
controller's profile's.

All of it is kept already, or nearly. Whether a stick's four binds
are usable is now kept with the port's keys (two bits, worked out
when a bind changes). Which of the four keys are down is four bits of
what the port's keys hold for every bind, asked of the driver once a
poll. The pad's axis and button behind each are in the port's
resolved set - and looked for there only where some controller's pad
could be behind the stick at all, so a port with no controller has
nothing made for it. input_stick_src() gathers the three for both
readers, which take the port for it.

Two cases still go to the records: a read by another controller than
the port's own (for the pad's axes and buttons), and an input driver
that does not say which of a list of keys are down - nine console
drivers - for which nothing is kept of its keys, so the stick is read
as it always was.

One thing changes: a stick's keys were read from the first port's
keyboard whichever port the stick was; they are the port's own keys,
as its buttons' are. The two are the same keyboard except where ports
have been given keyboards of their own.

Measured, the frame's input work over 300 frames (X11), with keys
bound to both sticks' directions as the test configuration has them:
- no controller: 4,011,432 instructions before, 3,800,502 after;
- a controller with a hotkey enabler: 4,678,863 before, 4,483,319
  after.
Most of it is the keyboard reads: four keys a stick, each through the
driver's state call, every frame.

The frontend harness gains a lane for where a stick's values come
from: the profile's axes; the port's own bind over the profile, and
the profile again when it is taken away; a pad's button behind a
direction; nothing from a stick one of whose binds is not usable, the
other stick reading beside it, and the stick again when the bind is
usable. Its first run crashed on a driver without a key list, which
is how the second case above came to be handled.

Checked besides: the X11 keyboard session, which presses the keys
bound to the sticks - each way, both at once, the other stick - logs
the same as before; the hotkey and quit-combination sessions log the
same lines on master's binary and this one; the harness passes every
lane.

The fifth piece of the mapping compiler's fifth step (plan, section
21). Counted as before, binds are read from the tables at 118 places
in 29 functions of this file, from 130.
2026-10-07 16:30:06 +02:00
libretroadmin b076226b6e android: ask the XR context by name instead of taking video state
The input/video separation gate holds input/ to zero
video_state_get_ptr() calls, and the INIT_WINDOW handler had the one:
reaching into the video state to string-compare the current context
ident against android_vk_openxr before deciding whether a new window
requests a reinit. The question belongs to the context driver, so it
now answers it by name: android_vk_openxr_active() is true exactly
while that context is current - its XR instance lives from init to
destroy - and the handler keeps the same behaviour, suppressing the
reinit only while the XR context holds its session across window
recreation. Both separation checks pass again, with the grab check's
counts unchanged.
2026-10-07 16:24:40 +02:00
Craigandlibretroadmin ebfc5c7652 VR support - Quest to begin with (#19726)
* deps: add openxr-sdk

* openxr: initial implementation

* openxr: build the vendored loader from the tree, drop the CMake step

The OpenXR loader is compiled like every other vendored dependency
instead of through a cmake invocation run from a $(shell) at Android.mk
parse time against one hardcoded NDK path: griffin_cpp pulls in the
loader, jsoncpp and (on Android) the jnipp/android-jni-wrappers broker
sources, griffin.c picks up the generated C dispatch table, and the
loader links statically into retroarch-activity, so the prebuilt
libopenxr_loader.so module and its packaging disappear. The loader is
C++17, so the quest build appends -std=gnu++17 after the global gnu++11.

Makefile.common gains an equivalent HAVE_OPENXR block (platform-neutral
sources only) and qb grows a disabled-by-default HAVE_OPENXR switch, so
a future desktop OpenXR backend builds the same loader through configure
rather than growing a second build path.

* openxr: move the vendored loader onto libretro-common threading

The loader's C++11 threading (std::mutex, std::shared_timed_mutex, and
jnipp's std::atomic_bool) is replaced with rthreads slocks and
retro_atomic. Cold paths - the global loader lock, the property
overrides, the debug-messenger map - keep plain mutual exclusion as
lazily-created slocks published with a CAS. The two read-mostly
structures become lock-free: the runtime dispatch table is a single
acquire/release-published slot, since the loader only ever has one live
runtime instance, and the logger's recorder list is an immutable
snapshot that the LogMessage paths walk wait-free while writers rebuild
and republish it under a cold slock, retiring superseded snapshots and
removed recorders until teardown so a reader never touches freed
memory. One behavioural delta, noted in loader_logger.hpp: a removal no
longer waits for in-flight readers, so a log call racing it can reach
the removed recorder once more before it drops out of view.

The per-frame xr* dispatch (the generated trampolines through the
cached dispatch table) was already lock-free and is unchanged; this
puts the whole tree on one threading layer and drops the libstdc++
thread machinery from the loader. Verified with per-TU and
griffin-shape amalgamated C++17 builds of every touched TU, plus a
TSan and ASan/UBSan/LeakSan harness driving concurrent LogMessage
readers against recorder add/remove churn and concurrent first-use
slock creation.

* openxr: route Vulkan instance/device creation by context, not build

On a HAVE_OPENXR build every VkInstance and VkDevice went through
xrCreateVulkanInstanceKHR/xrCreateVulkanDeviceKHR unconditionally, so
the flat vk_android fallback the driver takes when a session is
unavailable could never create a working context, and the queue-family
probe still queried surface support with a null surface after computing
the headless answer. Creation now routes through OpenXR only when the
live context is android_vk_openxr - everything else keeps
vkCreateInstance/vkCreateDevice - and vulkan_common reaches the XR
instance and system id through accessors on the context driver instead
of another TU's static, which only linked because of griffin include
order. The headless surface check is a single call that never passes
VK_NULL_HANDLE.

In vulkan.c the swap-interval emulation and BFI frame-duping recursion
are skipped under an XR session: the compositor owns pacing, and
re-entering vulkan_frame would issue a second xrBeginFrame before
xrEndFrame (the context's begin_frame also guards against re-entry
now). The HW fence wait keeps one shared condition with XR added inside
a single #ifdef so the non-XR build preprocesses exactly as before, the
dangling HAVE_OPENXR_2D probe is gone, and the eye loop and
vulkan_frame_eye declarations are C89.

* openxr: fix quest input regressions, make shared state cross-thread safe

android_joypad_state's axis-to-button path required port 0 under
HAVE_OPENXR, so physical pads on ports 1+ lost all analog input on the
quest build; it now goes through android_joypad_axis, which merges the
XR sticks for port 0 and falls through to the plain Android state for
every other port. Window motion events are only suppressed while an
OpenXR session is actually live instead of being compiled out (the
unreachable duplicate source check is gone), the never-defined HAVE_XR
gates are removed so the file matches the behaviour the quest build has
actually been tested with, and the INIT_WINDOW reinit check is braced
with its declaration at block top for the C89 lanes.

The shared OpenXR input state (buttons, axes, attach flag, menu long
press) is written by openxr_input_sync on the frame loop's thread -
the video thread under threaded video - and read by the input drivers
on the main thread, so it moves onto retro_atomic with a swap-consumed
long-press flag. The shared readers also lose their android_vk_ prefix
(they serve the GL path too) and live in openxr_input.h; the long-press
hotkey check in input_driver.c gains the ANDROID guard its definition
has always had.

* openxr: report VR availability honestly, decouple GL path from Vulkan

gl2's set_vr_content_info returned true unconditionally, so a core
asking for VR on the flat fallback context was told a session existed
and got a 0x0 recommended eye size; it now reports unavailable unless
the OpenXR context owns a live session, video_driver refuses and
unwinds a request when no eye target size is available, and the Vulkan
driver treats a NULL info as the documented disable instead of a probe,
clearing its stereo state. The unused RUNLOOP_FLAG_CORE_VR_CONTENT is
dropped: it sat on the deliberately retired bit 31 (1 << 31 in a signed
enum besides), and the bool the code actually uses stays, with its
comment naming the real environment calls.

griffin no longer nests the GL OpenXR context, the shared OpenXR input
TU and the loader dispatch table under HAVE_VULKAN, so a GLES-only
HAVE_OPENXR build links instead of failing on prototypes. The GL
context's end_frame declarations move to block top for the C89 lanes,
the stray C++ comment is gone, and the magenta debug clear on the eye
buffer is black.

* android: keep quest manifest entries out of the shared manifest

The Oculus app id meta-data and the installLocation flip to auto landed
in the manifest every Android build ships. The app id moves to the
quest flavor's overlay manifest where it belongs, and installLocation
returns to internalOnly, which is set deliberately: external-storage
installs have broken content paths and services before. If the quest
build needs auto, it can set it in its own overlay.

* deps/openxr: trim the vendored SDK to what the tree builds

The full SDK checkout came with its own CI configs, CMake build system,
gradle/prefab/vcxproj packaging, jsoncpp's test suites, examples,
docs and devtools, jnipp's project files, and the unused non-core
dispatch table. All of it goes: what stays is the OpenXR headers, the
loader and common sources the tree compiles, the lib_json sources and
headers, jnipp and the android-jni-wrappers, the generated core
dispatch table, and every license file (LICENSE, COPYING.adoc, the
LICENSES directory and REUSE sidecars of kept files, jsoncpp's and
jnipp's licenses). README.md stays as provenance for the vendored
version.

* ci/android: carry the quest flavor's debug apk as an artifact

assembleDebug already builds every flavor, so the quest build compiles
on each CI run now that the loader builds through ndk-build with no
host cmake step; uploading its apk makes a quest regression visible in
the artifact list and gives testers a build to sideload. openxr_input
also includes verbosity.h itself instead of leaning on griffin include
order for RARCH_ERR.

* openxr: parse manifests with libretro-common rjson, drop jsoncpp

The loader's JSON use is three manifest/broker walks over a small value
tree, so a ~250-line Json::Value subset backed by rjson (loader_json)
replaces the vendored jsoncpp - a megabyte of C++ the binary already
duplicated, since rjson is compiled into every RetroArch build. The
shim keeps jsoncpp's names and the exact semantics the loader relies
on: missing members read as a shared null, mutable indexing creates
members, extension_version accepts both its string and integer
spellings, and the parser takes comments, trailing data and a UTF-8
BOM like the CharReaderBuilder defaults did, so nothing previously
loadable becomes unloadable.

Verified against the old implementation as an oracle: a harness driving
RuntimeManifestFile and ApiLayerManifestFile over valid, relative-path,
malformed, wrong-version, field-missing, trailing-data and commented
manifests produces byte-identical results on both, runs clean under
ASan/UBSan/LeakSan, and the quest, GLES-only and OpenXR-off NDK builds
all still link.

* android: declare the OpenXR runtime brokers and their permissions

The vendored loader discovers the active runtime by querying the
ContentProviders at org.khronos.openxr.runtime_broker and
org.khronos.openxr.system_runtime_broker (android_utilities.cpp). The
quest flavor targets API 34, where package-visibility filtering hides
any provider authority the app does not declare, and the manifest
carried no queries element at all, so broker resolution was working
only at the OS vendor's grace. Declare the two authorities the loader
actually queries, together with the Khronos broker permissions - both
normal protection level, granted silently at install with no user
prompt - so the generic installable broker also serves the app, not
just Meta's unenforcing one.

Everything lives in the quest overlay, where the loader ships: the
headtracking uses-feature in particular must never migrate to the
shared manifest, since required=true there would delist the app for
every non-VR device on the Play store; a comment now says so.

* jni: cleanroom rjni layer in libretro-common, used tree-wide

All JNI plumbing moves behind one cleanroom libretro-common layer:
rjni registers the process JavaVM once (CAS, first wins), caches each
thread's JNIEnv in destructor-capable TLS - GetEnv tells threads the
VM already owns, which are left alone, from threads rjni attaches,
which are detached again when they exit - and provides lock-free
once-caches for class global references, method IDs and field IDs,
the VM crossings that make JNI call sites expensive. The exception
guard switches to ExceptionCheck, which creates no local reference.
The call wrapper macros move here verbatim from platform_unix.h, so
every JNI user in the tree shares one header.

platform_unix keeps jni_thread_getenv as the entry-point name but it
is rjni_env now, with the local key machinery deleted, and the
KeyCharacterMap path resolves through process-lifetime rjni caches,
so a driver restart re-resolves nothing. Verified with a fake-VM
harness under TSan and ASan/UBSan/LeakSan (8 threads, attach/detach
balance asserted), a real OpenJDK 21 VM exercising the caches from
four native threads, the C89 lane, NDK arm64 at -Werror, and MinGW.

* openxr: drop jnipp and android-jni-wrappers, query the broker through rjni

The loader's two remaining vendored C++ dependencies existed only to
make JNI calls, which the tree now does through libretro-common's
rjni, so android_utilities is rewritten on raw JNI with an rjni
once-cache for every broker class, method and field - same URIs, same
log lines, same installable-then-system fallback - and
loader_init_data reads the asset manager and nativeLibraryDir the same
way (a dead getApplicationContext call did not survive the rewrite).
With jnipp gone this also deletes the only exception-throwing code in
the loader, so the Android path is safe under -fno-exceptions
unconditionally, and the last C++11 threading construct, jnipp's
thread_local, leaves the tree with it.

jnipp and android-jni-wrappers are removed from deps, griffin and the
Android include paths. Both rewritten files pass standalone NDK arm64
syntax checks, the quest ndk-build links, the GLES-only and plain
shapes still build, and the Windows-shape loader amalgam still
compiles under MinGW.

* openxr: filesystem code on libretro-common file_path/retro_dirent

filesystem_utils keeps its interface but its three implementation
branches - C++17 <filesystem>, experimental, and the per-platform
fallbacks - are replaced by one body over libretro-common's
file_path, path_stat and retro_dirent, matching the std::filesystem
semantics this build shipped (parent/trailing-slash decomposition,
absolute-child replacement in CombinePaths, the Windows canonical
no-op). stdfs_conditions.h goes with the branches. Three behavioural
deltas, each for the better: FindFilesInPath and GetCanonicalPath
return false on a missing path where directory_iterator and
std::filesystem::canonical threw (the loader builds -fno-exceptions
elsewhere), and ParsePathList no longer corrupts the middle entries
of XR_API_LAYER_PATH lists with three or more paths, an upstream
substr count bug present in every branch.

Verified by a function-level oracle, old implementation against new
over a fixture tree (files, subdirs, symlinks, dangling links, path
lists): byte-identical except the two corrupted-list lines, now
correct; ASan/UBSan/LeakSan clean; and the manifest-file oracle from
the rjson conversion rerun on the new filesystem, byte-identical
including relative library_path resolution. Quest ndk-build links
and the Windows-shape loader amalgam compiles under MinGW.

* openxr: remove the loader's exception handling

Nothing in the loader throws any more - jsoncpp, jnipp and the
std::filesystem calls went in earlier changes - so the exception
machinery guarding the C ABI goes too: exception_handling.hpp and the
71 XRLOADER_ABI_TRY/CATCH wrappers around the entry points in
loader_core and xr_generated_loader, the empty catch blocks in the
ApiLayerInterface and RuntimeInterface destructors, and the dead
jsoncpp exception-mode guard in manifest_file. The whole loader now
compiles under -fno-exceptions, which the compiler itself enforces as
proof that no try, catch or throw remains; griffin keeps building it
with exceptions enabled only because glslang shares the TU. The one
reachable delta is allocation failure inside an entry point, which
terminates instead of unwinding to XR_ERROR_OUT_OF_MEMORY - the
loader is statically linked into the frontend, where that is already
the outcome everywhere else in the process.

Verified with -fno-exceptions compiles of the full loader in Linux,
MinGW and NDK arm64 shapes, the manifest-file oracle byte-identical
under ASan/UBSan/LeakSan, and quest plus plain ndk-builds linking on
the PR's rebased head.

* openxr: filesystem_utils on direct libretro-common calls where one fits

Every function here is now a direct libretro-common call where one
with std::filesystem's semantics exists: PathExists is
path_is_valid, CombinePaths joins through
fill_pathname_join_special, and the hand-rolled separator macros
give way to PATH_CHAR_IS_SLASH and PATH_DEFAULT_SLASH_C; only the
env path-list separator keeps a local define, since libretro-common
has no such constant. What stays hand-written is documented in
place as a deliberate semantic difference from libretro-common's
contracts: GetParentPath (fill_pathname_basedir fabricates "./"
for a bare filename and keeps trailing slashes), GetAbsolutePath
(path_resolve_realpath without symlink resolution would normalise
dots, which std::filesystem::absolute does not), ParsePathList
(string_split's strtok collapses empty entries), and
FindFilesInPath wants bare names where dir_list_new returns full
paths, so the retro_dirent primitives are the fit.

FileSysUtilsGetCurrentPath had no caller anywhere in the loader and
leaves the interface, surviving only as the static getcwd helper
behind GetAbsolutePath. The last two <stdexcept> references go too:
manifest_file's include was dead since the exception removal, and
vulkan_debug_object_namer.hpp was referenced by nothing in the
tree. Both oracles rerun byte-identical - the function-level one
over the fixture tree and the manifest-file one under
ASan/UBSan/LeakSan - and the loader still compiles -fno-exceptions
in Linux, MinGW and NDK shapes, with quest and plain ndk-builds
linking.

* openxr: guard the context drivers' XR platform defines

The Android build also passes XR_USE_PLATFORM_ANDROID on the command
line, so the bare defines the two OpenXR context drivers carry for
standalone compilation redefine it - and the graphics-API macros
alongside it are one -D away from the same collision. Wrap all of
them in #ifndef so either source of the define wins quietly,
whichever compiles the file first in the griffin TU.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-10-07 16:24:05 +02:00
LibretroAdmin 9a5a29545c input: the lightgun's buttons and mouse-button binds are read from what is kept
Two more readers of the bind tables on the frame's path.

A bind's mouse button. The port's keys keep, for each bind, whether it
names a mouse button (has_mbutton); which button was asked of the
bind's record each time one was read. It is kept beside the bit
(mbutton[], written where the bit is set), and the RetroPad's
mouse-button binds and the lightgun's read it there.

The lightgun's bound buttons. For each one read, the bind's record was
looked at for whether it is usable, its pad button and its axis, and
the profile's record for the same two. They are read from the port's
resolved set, as the RetroPad's and the hotkeys' are - and only where
some controller's pad could be behind the bind at all, so a port with
no controller has nothing made for it. A read by another controller
than the port's own goes to the tables as before.

One thing changes with that: a lightgun button with nothing of its
own, answered from a controller's profile, used the profile at the
port's number; it uses the profile of the port's controller, which is
what the pad is read from and what the RetroPad's buttons go by. The
two are the same unless a port's Device Index names another
controller.

Measured, the frame's input work over 300 frames (X11):
- no controller: 4,015,817 instructions before, 4,011,432 after;
- a controller with a hotkey enabler: 4,681,707 before, 4,678,863
  after.
A first version made the port's resolved set for a port with no
controller, 12,000 instructions at the start for nothing; hence the
test of whether a pad could be behind the bind.

Checked: the X11 keyboard session, whose core reads the lightgun's
buttons every frame, logs the same as before; the hotkey and
quit-combination sessions log the same lines on the binary before
this and the one after; the frontend harness passes every lane, the
lightgun-buttons lane (a key, a mouse button, a pad's button) among
them.

The fourth piece of the mapping compiler's fifth step (plan, section
21). Counted the same way as at its start, binds are read from the
tables at 130 places in 29 functions of this file, from 154 in 30.
The largest left on the frame's path are the sticks, and the places
that hand a port's binds on or fall back to them.
2026-10-07 16:04:44 +02:00
LibretroAdmin ad7b211325 input: the hotkey blocking rules read a kept set, not the bind table
"Hotkey Enable" blocks a hotkey that is pressed by rules that ask the
hotkey's own bind three things - whether it has a pad's button or axis
of the port's own, what its key is, whether it is usable - and asked
the bind table each time; the pass asked the last of every hotkey it
went through.

The hotkeys are read on one port, so there is one set of those
answers for it (input_hotkey_set_t: two words of "usable", two of
"has a pad of its own", a key a hotkey), made when a bind changes or
the hotkeys move to another port. Whether the set is the port's and
current is a bit in the port's kept answers, which a frame has in
hand already: the port whose answers are worked out again loses the
bit with them, and the port the set is taken from is told.
input_hotkey_blocked() reads no table, and nor does the pass's loop.

A first version gave the set a change count of its own to compare
each frame, and cost five instructions a frame more than the table
reads it replaced. This one costs none.

Measured, the frame's input work over 300 frames (X11):
- no controller: 4,017,937 instructions before, 4,015,817 after;
- a controller with a hotkey enabler: 4,688,647 before, 4,681,707
  after.

The harness's hotkey lane gains the set changing owner: the hotkeys
moved to a second port, whose pause hotkey is unusable, and back, by
a setting alone ("Hotkeys Follow Player 1") - the first port's pause
hotkey must work again. With the telling of the old owner taken out
of the new code, the lane fails there.

Checked besides: the X11 keyboard session's log is the same as
before; the hotkey session (with and without the enabler and Game
Focus) and the quit-combination session log the same lines on
master's binary and this one; the harness passes every lane.

The third piece of the mapping compiler's fifth step (plan, section
21).
2026-10-07 16:04:31 +02:00
LibretroAdmin e6f4ba827f metal: lend streamed textures their staging slots
A streaming surface on Metal wrote each frame into its own slot, which
the update then copied into the texture's staging buffer for the
blit. The driver now lends the first two staging slots: the producer
writes the frame there and the blit reads it with no copy on the CPU.
A lent slot is ready when its last blit's command buffer has
completed, the flag the update already keeps; an update from other
memory goes only through the slot not lent, and one from a lent slot
written before its blit finished is dropped. The staging buffer is
made by whichever comes first and is not grown under a lent slot.

The harness's lend lane holds metal to lending, as it does the other
drivers that lend.
2026-10-07 15:34:28 +02:00
LibretroAdmin fb835d9dc1 msvc-6: the project builds a joypad driver
HAVE_WINMM_JOYPAD: controllers through the multimedia library's
joystick calls, which need nothing the Windows this project targets
lacks and no library it does not already link.

With it the project names a driver of every kind: GDI for the
picture, wave output for sound, plain Win32 for the keyboard and
mouse, and this for controllers.

tools/msvc6_standin_check.sh builds and links it; its workflow also
runs the driver's test.
2026-10-07 15:24:53 +02:00
LibretroAdmin 6e6db8d17b input: a joypad driver for the multimedia library's joystick calls
"winmm": controllers through joyGetPosEx() and the rest of winmm.dll,
which Windows 95 and NT 4.0 have with no DirectX. A build for them had
no joypad driver at all - DirectInput 8 is not there - and so no
controllers.

Up to sixteen controllers, as the library numbers them; six axes,
thirty-two buttons and one hat each. An axis is scaled across the
range the controller itself gives; the hat's angle becomes the nearest
of the eight ways. No rumble: the library has none.

A controller is asked where it is once a poll. One that is not there
is not asked every poll - the question is slow on these systems when
the answer is no - but looked for again once a second, by the clock
and right across its wrap; that is how one plugged in later is found,
and one that stops answering is let go at that poll. Each is announced
to autoconfiguration by its name and maker's numbers.

Built where HAVE_WINMM_JOYPAD is defined (HAVE_WINMM_JOYPAD=1), which
nothing turns on by default but the Visual C++ 6 project in the commit
after this. Where there is no DirectInput it is the default.

No controller can be scripted here, so samples/input/winmm_joypad
runs the driver with the library's calls and the clock replaced by
the test's: a controller announced once, and one only its driver
answers for not at all; axes end to end across three different
ranges, from the side asked, and an axis the controller lacks reading
nothing; the first and last of thirty-two buttons; the hat's eight
ways and centred; the RetroPad mask from a port's bind and a
profile's; pulled out, plugged in, found after a second and not
before; the fifteen that are not there not asked in ten polls.

Run besides: the Windows build with it compiled in starts under Wine
with it selected, finds no controller, and takes the scripted keyboard
session as before. Never run with a controller, on Wine or on Windows.
2026-10-07 15:24:50 +02:00
LibretroAdmin f6a1ed40c9 threaded_video: a lane for gl2's linear scRGB draws
gl2 draws a half-float texture under HDR output through a program that
writes the inverse of its encode, into an RGBA16F layer - the frame's
for an SDR source, the UI's own for a PQ one. No runner has an HDR
backbuffer, so the lane marks scRGB active with no encode program,
which leaves the frame in those layers, draws a flat texel above menu
white over the whole layer through the display driver, and reads it
back: it must be the encode's inverse at the menu nits, for the plain
gamut and the one that rotates through Rec.2020, for both sources.
2026-10-07 15:17:55 +02:00
LibretroAdmin 90818298c8 msvc-6: the project builds an input driver
HAVE_WIN32INPUT: the keyboard and the mouse from plain Win32, the
input driver that needs nothing the Windows this project targets
lacks. With the GDI video and wave output audio drivers it already
names, what the project builds shows a picture, plays sound and takes
keys.

tools/msvc6_standin_check.sh builds and links it; its workflow also
runs the driver's test under Wine against a window, and counts a part
the test had to skip as a failure.

Still without controllers: a joypad driver for these Windows is next.
2026-10-07 15:15:21 +02:00
LibretroAdmin cb20568114 input: a driver for the keyboard and mouse from plain Win32
"win32": the keyboard and the mouse from the window and the oldest
calls of the Windows API. Raw input needs Windows XP and DirectInput 8
needs DirectX 8; a build for Windows 95 or NT 4.0 had neither, so it
had the null input driver and could not be given a key.

- Keys. The window procedure sees every key go down and come up and
  already turns each into a scancode for the key events it sends. It
  tells this driver the same (win32_input_key_message(), one call
  where the scancode is worked out): a bit a scancode. The window can
  be the video thread's, so the bits are atomic words - set and
  cleared there, loaded at the poll - and nothing is locked. The
  frontend asks for the keys it has binds for once a poll
  (keys_down), as it does of the other keyboard drivers.
- Its key map (rarch_key_map_win32) is DirectInput's in numbers:
  scancodes, the extended keys with 0x80 added. No DirectX header.
- Mouse. Where the cursor is in the window at each poll, and its left,
  right and middle buttons (GetAsyncKeyState(), the two swapped where
  the user has them swapped), published for the frontend to answer the
  mouse, the pointer and the lightgun from.
- Only while the window is the foreground one. Without it nothing is
  down, and a key held when the window lost the foreground is not down
  when it has it back.

What it does not have: a wheel, buttons four and five, motion past
the screen's edge (motion is how far the cursor went), a grab. And no
controllers - that is a joypad driver's, and the one for these
Windows, the multimedia library's joystick calls, is not written yet.

Built where HAVE_WIN32INPUT is defined (HAVE_WIN32INPUT=1), which
nothing turns on by default but the Visual C++ 6 project in the
commit after this. It is chosen by name, or where neither raw input
nor DirectInput is built or starts.

Run: the Windows build with it compiled in, under Wine, through the
scripted keyboard session - the core sees the same 63 lines with
"win32" as with "dinput", and DirectInput's own are the same as
before this. samples/input/win32_input runs the driver against a
real window under Wine: the key map both ways, keys down and up, an
extended key against the keypad key under it, the top bit of a word,
nothing down with another window in front, the mouse's place and
motion. Not run on a Windows machine, of any age.
2026-10-07 15:15:15 +02:00
LibretroAdmin c5ce234829 statistics: hide Deviation and show Pushed under threaded video
Frame times are not sampled while threaded video is on, so the
-Deviation line is left out then. Under the threaded wrapper a
-Pushed line follows Frames, reporting the pushes that took a free
slot (the wrapper's hit count, via the handoff stats); the
threaded_video pacing-drain lane checks it matches the wrapper.
2026-10-07 15:00:31 +02:00
LibretroAdmin 9e2b9c8970 gl2, glcore: lend streamed textures their upload buffers
A streaming surface on the desktop GL drivers wrote each frame into
its own slot, which the update then handed GL to copy. Where buffers
can be mapped for good (GL 4.4 or ARB_buffer_storage), the drivers
now lend a texture two pixel unpack buffers, mapped persistently and
coherently: the producer writes the frame there, the update reads it
from the buffer with no copy on the CPU, and a fence per buffer says
when its upload has run, so texture_lend_ready never waits. The
buffers go with the texture, and an update of a buffer written before
its fence signalled is dropped. The code is shared in gl_common.c.

The harness's lend lane now holds gl and glcore to lending, as it does
vulkan and d3d12, and on them reads back the last frame of each direct
pass to check the texture holds what was written to the lent buffer.
2026-10-07 14:58:23 +02:00
LibretroAdmin a862e3b43b msvc-6: the project builds a video and an audio driver
The Visual C++ 6 project named no driver of any kind, so what it built
had the null video, audio and input drivers: no picture, no sound, no
input. It defines HAVE_GDI and HAVE_WAVEOUT now - the GDI video driver
and the wave output audio driver, neither of which needs anything a
Windows without DirectX lacks, or any library the project does not
already link.

tools/msvc6_standin_check.sh builds and links the project with both.
Its workflow also runs the audio driver's test under Wine against a
silent device from a sound server it starts; with no device that test
only skips, which the step counts as a failure.

Still to come: an input driver. The project has none - DirectInput 8
is not there on the Windows it targets - so what it builds shows a
picture and plays sound and cannot yet be given a key.

Built and linked as a 32-bit program on the stand-in compiler; not
started. The Wine here does not run 32-bit programs.
2026-10-07 14:57:42 +02:00
LibretroAdmin c81e864380 audio: a driver for the multimedia library's wave output
"waveout": audio through waveOutOpen() and the rest of winmm.dll,
which every Windows has had since 3.1. It is the only audio a build
for Windows 95 or NT 4.0 with no DirectX can count on, and there was
no driver for it: such a build had the null driver and no sound.

The device is given a ring of eight blocks sized to the latency asked
for. write() fills a block and hands it over when full; the device
plays them in order and hands each back marked done, setting an event.
There is no callback function, so nothing of the driver's runs on the
device's thread and nothing is locked: a block is the driver's or the
device's, and the done mark is the one word that crosses. A blocking
write waits on the event, for a bounded while; one that must not
block returns short. 16-bit stereo; it reports its buffer, what it
will take now, and the frames the device has consumed.

Built where HAVE_WAVEOUT is defined (Makefile.common: HAVE_WAVEOUT=1),
which nothing turns on by default but the Visual C++ 6 project in the
commit after this. Where it is the only real audio driver built, it
is the default.

samples/audio/waveout runs it for real under Wine against a wave
output device: three seconds written with the device setting the
pace, what it will take never more than its buffer, frames consumed
only going up and ending within a buffer of what was written, a write
that must not block returning short and at once, a bounded wait for
room, stop and start. Passes here on a silent device. That device
starts half a second late and played three seconds of sound, queued
whole with no driver in between, in 3.9 - so the test asserts that
the write is paced, not how well.

Not run on a Windows machine, of any age.
2026-10-07 14:57:39 +02:00
LibretroAdmin 5c3be409e9 gdi: a build for before Windows 98 does not call AlphaBlend
The GDI video driver keeps AlphaBlend() for builds that target
Windows 98 or 2000 and up - it is in msimg32, which Windows 95 and
NT 4.0 do not have - and says so with

   #define GDI_HAS_ALPHABLEND (_WIN32_WINNT >= 0x0410)

which is always defined, and true or false. Two places asked whether
it was defined (#ifdef) where the rest ask whether it is true (#if):
the function that blends RGUI's frame, and the call of it. So a build
for an older Windows compiled both, called AlphaBlend() and did not
link - or, linked against msimg32, would not start on the Windows it
was built for. They ask #if now, and such a build takes the opaque
copy the comment beside them describes.

Found by building the Visual C++ 6 project's configuration with GDI
on a stand-in compiler (tools/msvc6_standin_check.sh): with this it
links without msimg32.
2026-10-07 14:57:35 +02:00
LibretroAdmin ddd7457e1c CI: what the Visual C++ 6 project builds is built, on a stand-in compiler
pkg/msvc/msvc-6 is a project for a compiler no CI machine can be
given, so nothing built it and it stopped compiling without anyone
seeing (the commit before this).

tools/msvc6_standin_check.sh takes the project at its word. It reads
the .dsp - the two sources it names (griffin.c, griffin_cpp.cpp), the
defines on its compiler line, the libraries on its linker line - and
compiles and links the same with a 32-bit mingw compiler held to old
C: GNU C89 with -pedantic an error and no declaration after a
statement, at Windows NT 4.0's API, linked for real against the
project's libraries and the two the sources ask Microsoft's linker
for by #pragma.

It catches what does not need the real compiler: a define the project
lacks that some code now needs, newer C or a call newer than NT 4.0
in something the project compiles, a function called and compiled
out. On master before the fix it stops at command.c; after it, it
passes.

It does not catch what only Visual C++ 6 objects to:
- "long long" and variadic macros are let through, as in the tree's
  own C89 job. The tree spells both for that compiler under _MSC_VER,
  and a stand-in cannot be _MSC_VER 1200: tried, gcc does not parse
  what is written for it (1,469 errors, "ui64" and the like);
- so the code under "#if _MSC_VER <= 1200" is never read;
- the project says NT 3.51 (_WIN32_WINNT 0x0351); mingw's headers do
  not hold together below 4.0, so 4.0 it is.
2026-10-07 14:36:33 +02:00
LibretroAdmin 05071b6083 msvc-6: the project does not define the network commands without networking
The Visual C++ 6 project defined HAVE_NETWORK_CMD and not
HAVE_NETWORKING. command.c takes the first to mean it may use the
sockets the second brings in, so with the one and not the other it
does not compile: socklen_t, socket_init(), struct addrinfo and the
rest are not declared. The 2003 and 2005 projects define both; this
one has no networking - its linker line names no socket library - and
so no network command interface either.

Found by building what the project builds on a stand-in compiler
(the next commit); with the define gone that build compiles and links.

What the project's defines come to, for the record: it names no video,
audio or input driver (no HAVE_GDI, no HAVE_DSOUND or other audio, no
HAVE_DINPUT), so the program it builds has the null driver of each.
Left as found.
2026-10-07 14:36:29 +02:00
LibretroAdmin 5129d6f84c input: an input driver's state call takes one joypad driver
input_driver_t::input_state was handed two joypad drivers: the one
there is, and the secondary, "for secondary input devices (eg MFi
controllers)". The frontend no longer has a secondary and passed NULL
for it at each of its nine calls; no driver did anything with it but
silence the warning about not using it.

The parameter is out of the interface, of the twenty-nine functions
that implement it - every input driver's, the null driver's and the
test stand-ins' in samples/ - and of the nine calls, and of the one
call a sample makes of a driver's function directly
(winraw_keyboards_test, which the sample link check caught). Nothing
else changes in any of them.

Every parameter list of that call in the tree was checked by script
for the new shape, including the drivers that cannot be compiled
here. Compiled: the frontend and the Linux drivers (X11, udev,
Wayland, SDL 1/2/3, linuxraw, test) with a table entry of the wrong
shape made an error; Windows (DirectInput, raw input); Android; the
Cocoa driver for macOS; PS4 and QNX against their stand-in headers.

Measured, the frame's input work over 300 frames (X11): 4,019,788
instructions to 4,017,937 with no controller, 4,690,524 to 4,688,673
with one and a hotkey enabler.

Checked: the X11 keyboard session's log is the same as before; the
hotkey and quit-combination sessions log the same lines on the
binary before this and the one after; the frontend harness passes
every lane; tools/sample_link_check.sh builds the nine samples that
link the changed files, and winraw_keyboards_test passes under Wine.

With this the secondary joypad is gone from the tree.
2026-10-07 14:16:15 +02:00
LibretroAdmin a90f05d13a input: one copy of the pad state, where there were two for two joypad drivers
The frontend's per-poll copy of the pads, and what it works out from
it, were kept twice: once for the primary joypad driver and once for
the secondary. The secondary is gone, and nothing ever used its copy.

- input_snapshot_bridge is one bridge, not an array of two: 2,040
  bytes instead of 4,080. Its functions take no number to say which
  bridge, so they have a joypad driver's own shape and stand in the
  adapter's table themselves - the eight wrappers that added the
  number are gone.
- the masks worked out from the copy (pad_state_cache, pad_state_thr,
  and their bits in frame_valid) and the sticks read once a frame
  (stick_cache) are by port and by stick, not by driver as well:
  input_driver_state_t is 240 bytes smaller.

2,280 bytes in all, and 43 lines.

Measured, the frame's input work over 300 frames (X11): with no
controller 4,020,362 instructions before, 4,019,788 after. With a
controller and a hotkey enabler 4,690,515 before and 4,690,524 after:
the stick read and the pads' set are 646 cheaper, and the poll, which
clears and marks the smaller set of bits, came out 600 dearer as
compiled here.

Checked: the X11 keyboard session's log is the same as before; the
hotkey and quit-combination sessions log the same lines on master's
binary and this one; the frontend harness, which reads its stand-in
pad through the bridge, passes every lane. Compiled with and without
HAVE_MFI.
2026-10-07 14:16:10 +02:00
LibretroAdmin e948353099 overlay: the ownership check knows the drivers' own borrowed fields
The load_textures drivers added since keep their marker as a field of
their own - overlay_borrowed, overlays_borrowed - rather than a flag
bit or an overlay struct's "borrowed", so the check found no marker in
nine drivers that set, test and clear one. It takes those names now.
d3d8 tested its marker only to null the texture before the common
free; it frees through a negated test like the rest, releasing just
the vertex buffer of a page it borrowed.
2026-10-07 14:15:28 +02:00
LibretroAdmin 5e0af2d107 gl2: linear scRGB textures over PQ content too
With a PQ source the UI draws into its own layer, which the encode
composites over the decoded content at the menu nits. That layer now
turns RGBA16F once a linear texture has been loaded, as the SDR
content's layer does, so TEXTURE_GPU_FORMAT_SCRGB holds for both
sources and a linear texture keeps what is above menu white.
2026-10-07 13:46:28 +02:00
LibretroAdmin f6203ec2e7 input: the secondary joypad is gone from the frontend
The secondary joypad stood in for a second joypad driver on iOS and
macOS, when there was no other way to have two: a second pointer
beside the first, read after it and or'ed over it wherever a pad is
read, polled, asked to rumble or taken down.

Nothing gives it a value. In the whole tree
input_driver_state_t::secondary_joypad was only ever set to NULL, and
outside HAVE_MFI builds most reads of it were compiled out. So every
branch on it was dead, and every function handed it along for nothing.

Out of input_driver.c and the state structure:

- the member, and the fourteen HAVE_MFI blocks that read or freed it;
- the ten "if (sec_joypad)" reads, polls and rumbles;
- the parameter of input_state_wrap(), input_state_wrap_slow(),
  input_frame_or_driver() and input_keys_pressed(), and of the
  blocking rules' context - thirty-five calls pass one argument fewer;
- input_driver_capture_pad()'s loop over two drivers.

213 lines. The input drivers' own state call still has the parameter,
and is passed NULL as before: taking it out of the interface touches
every input driver, and is the next patch. So is the second copy of
the per-driver pad state (input_snapshot_bridge[1] and the masks
beside it, about 2 KB and an index on every read), which was the
secondary's and is now only ever the first.

Measured, the frame's input work over 300 frames (X11) - less than
dead code should cost, because the argument was carried through every
read:
- no controller: 4,108,568 instructions before, 4,020,362 after;
- a controller with a hotkey enabler: 4,757,363 before, 4,690,515
  after.

Checked: the X11 keyboard session's log is the same as before; the
hotkey and quit-combination sessions log the same lines on master's
binary and this one; the frontend harness passes every lane. Compiled
for Linux with and without HAVE_MFI defined, for Windows and Android,
with overlays and networking off, and the Cocoa input driver for
macOS. Not run on a Mac or an iPhone.
2026-10-07 13:46:13 +02:00
LibretroAdmin 83d223b253 gl2: half-float textures, shown as linear scRGB under HDR output
Desktop GL takes RGBA16F textures from half floats where GL 3.0 or
ARB_texture_float and ARB_half_float_pixel have them, loads and
updates them in place, and answers TEXTURE_GPU_FORMAT_RGBA16F.

Under scRGB or HDR10 output with an SDR source, such a texture is
linear scRGB: the menu draws it through a program that writes the
inverse of the encode at the menu nits and gamut, into an SDR layer
that turns RGBA16F once a linear texture has been loaded, so what is
above menu white reaches the encode. A layer that can be RGBA16F,
probed at init, is what answers TEXTURE_GPU_FORMAT_SCRGB. While
recording reads the layer back it stays 8-bit.

The harness's fp16 lane counts refused updates, since GL hands a
replacement texture the deleted name back.
2026-10-07 13:37:38 +02:00
LibretroAdmin b8126375b4 input: the hotkey pass keeps what it asked the bind tables every frame
For every port, every frame, the pass read four binds out of the two
tables a dozen times: whether the controller and the keyboard have a
"Hotkey Enable", whether there is an enabler that blocks, whether the
menu toggle's button may be pressed without it, whether the menu
toggle and the enabler share a key, the menu toggle's key - choosing
each time between the port's own bind and its controller's profile's.
And for the port the hotkeys are read on, whether each hotkey it went
through is usable.

Those answers are kept by port now (input_port_hotkeys_t: a byte of
flags, the menu toggle's key, and for the hotkeys' port a bit a hotkey
for "usable"), worked out again when a bind changes or the port is
read with another controller's profile. A frame compares two words and
looks at the byte. input_keys_pressed() reads neither table, and its
caller no longer prepares two pointers into them for every port.

Measured, the frame's input work over 300 frames (X11):
- no controller: 4,124,574 instructions before, 4,108,568 after;
- a controller with a hotkey enabler: 4,764,421 before, 4,757,363
  after.
Kept: 32 bytes a port.

A lane in the frontend harness for what is kept going stale, with the
pause hotkey on a pad's button: an enabler bound to the port and
unbound, the hotkey made unusable and usable again, an enabler in the
controller's profile, and the port given another controller whose
profile has none - a setting, which no bind change announces. Each is
seen at the next press. With the compare of the profile taken out of
the new code, the lane fails at that last one.

Checked besides: the X11 keyboard session's log is the same as
before; the hotkey session (with and without the enabler and Game
Focus) and the quit-combination session log the same lines on
master's binary and this one; the harness passes every lane. Compiled
for Windows and Android.

The second piece of the mapping compiler's fifth step (plan, section
21). input_hotkey_blocked(), which runs when a hotkey is pressed under
an enabler, still reads the tables; the sticks and the lightgun's
buttons are after it.
2026-10-07 13:23:09 +02:00
LibretroAdmin cd05e5d996 overlay: show the pack's textures on gl1, sdl2, gdi, d3d8-10 and consoles
These drivers took load() on every overlay page switch, uploading and
freeing every image of the page each time. Each now implements
load_textures: a page draws from the textures the pack uploaded once,
borrowed and never freed by the driver, so a switch only resets the
page's geometry. ctr, gx2 and rsx draw from a copy of the texture
descriptor, gxm skips the GPU wait its own textures needed on a switch,
and gdi realizes the texture's DIB once for every page it is on.

gekko, gx, switch and hub75 load no textures, so they keep load().
2026-10-07 13:07:46 +02:00
LibretroAdmin 5fa973c0a4 input: a bind past the RetroPad's sixteen is read resolved, not from the tables
What is behind a bind - the port's own button or axis where it names
one, its controller's profile's where it does not - was chosen at
every read of a hotkey, a turbo or hold bind: the bind's record looked
at for whether it is usable, its button, its axis, and the profile's
record for the same two. For the RetroPad's sixteen that choice is
made when a bind changes and kept by port (input_port_pads_t).

It is kept for every bind now, with a bit for "usable and something
behind it", and input_state_wrap_slow() reads that for a bind past the
sixteen: a bit, and where it is set a button and an axis. Neither
table is looked at. A read by another controller than the port's own,
which the choice was not made for, makes it as it always was.

An entry is written only where something is behind the bind, which is
few of them, and read only there.

Measured, the frame's input work over 300 frames (X11):
- a controller with a hotkey enabler: 4,767,045 instructions before,
  4,764,421 after;
- no controller: 4,125,487 before, 4,124,574 after.
The first attempt cost more than it saved (4,778,434): it found the
port from the address of its binds, where the read already knows the
port.

The kept set grows by 496 bytes a port, 7,936 for sixteen, none of it
touched for a port no controller reads. That is against this step's
aim until the tables themselves shrink, which is what it is for: the
first piece of the mapping compiler's fifth step, the frame reading
the compiled form only (plan, section 21). Thirty functions in this
file read binds from the tables, at 154 places; this is the first of
the frame's.

Checked: the X11 keyboard session's log is the same as before; the
hotkey session (with and without the enabler and Game Focus) and the
quit-combination session log the same lines on master's binary and
this one; the frontend harness passes every lane. Compiled for
Windows and Android.
2026-10-07 12:58:06 +02:00
LibretroAdmin d82825cf3b gdi, gx2, gxm, ctr, rsx: update textures in place
A streaming surface on these drivers loaded a new texture every frame,
and on gxm and rsx every unload of the old one waited for the GPU to
go idle. Each now rewrites the texture it has when size and format
match, the way it already writes its menu texture: gdi premultiplies
into its buffer and the realized DIB, gx2, gxm and rsx copy rows into
the texture's memory, and ctr swizzles on the CPU straight into its
texture. ctr drops an update while a load's transfer is pending.
2026-10-07 12:44:22 +02:00
LibretroAdmin 42136125c8 CI: the QNX input drivers are compiled, against stand-ins for the SDK
RetroArch's QNX port is the BlackBerry 10 one: pkg/qnx is a QCC
project with HAVE_BB10, and the drivers use the BPS and screen
libraries. That SDK is not something a CI machine can fetch, so
nothing built the port - and its input driver stopped compiling
without anyone seeing: it asked whether `key` was down in a function
that declares no such name (fixed in the commit before this one).

tools/qnx_standin_check.sh compiles input/drivers/qnx_input.c and
input/drivers_joypad/qnx_joypad.c - declarations and syntax, nothing
linked or run - with the defines pkg/qnx builds with, against
stand-ins for the SDK's headers (tools/qnx_standin): types as opaque
pointers, constants as numbers, calls without their arguments. An
undeclared name, a call to a function that is not there or a driver
table that does not fit fails it. What depends on the SDK being real
does not.

Run on the driver as it was two commits ago it fails on the undeclared
`key`; on the tree it passes.

Not covered, and said so in the script:

- the variant without HAVE_BB10 (the PlayBook's). It calls
  qnx_input_autodetect_gamepad(), which only HAVE_BB10 defines: it
  cannot link, and has not for some time. Left as found.
- gfx/drivers_context/qnx_ctx.c, frontend/drivers/platform_qnx.c and
  audio/drivers/alsa_qsa.c, which use the SDK's structures by their
  fields; stand-ins for those would have to be written by hand.
2026-10-07 12:30:17 +02:00
LibretroAdmin 353a447fcd gl1, sdl2, sdl3: update textures in place
A streaming surface on these drivers loaded a new texture for every
frame. Each now rewrites the texture it already has when the size
matches (glTexSubImage2D, SDL_UpdateTexture), so a surface keeps one
texture for its lifetime. 10-bit and FP16 contents are refused, since
the textures hold 8 bits a channel.
2026-10-07 12:29:59 +02:00
LibretroAdmin 0ba39f9650 input: no driver reads the binds it is handed
The last two that did.

PS4: for the pad's buttons, and any other control, it read the bind it
was handed and asked the joypad driver - which the frontend has
already done for the same bind, the two answers being or'ed together.
Its input_state answers nothing now; the frontend's read stands.

QNX: it hands the frontend its keys (qnx_keys_down()), as the other
keyboard drivers do. What it had for the RetroPad's buttons and the
hotkeys checked a bind, then asked whether `key` was down - a name
that function never declares. The driver has not compiled.

tools/input_state_grab_check.py holds every file under input/drivers
to it, with a case in its self-test: a driver may pass the binds on,
and may not look inside.

Neither platform's SDK is here. Compiled against stand-ins for their
headers instead:

- PS4: clean, before and after. The file uses nothing of the Sony
  header it includes.
- QNX, in both its variants (with and without HAVE_BB10), with
  generated stand-ins for the screen and BPS types, constants and
  calls: master stops at the undeclared `key`; with this, no error.

Not run on either.

Why: how the binds are kept is now the frontend's alone to change.
The mapping compiler's third step - the configured binds without the
hotkey rows of users 2 to 16 - waited on this (plan, section 21).
2026-10-07 12:15:49 +02:00
LibretroAdmin 90e565ea55 input: the lightgun's bound buttons are the frontend's for udev and Cocoa too
A lightgun's buttons are what they are bound to: a pad's button or
axis, a key, a mouse button. The frontend answers them for the drivers
that publish their mice. udev and Cocoa keep theirs, and each worked
the buttons out itself, from the binds it was handed - the same code
twice over, and the frontend's a third time.

The frontend now answers for a driver that keeps its mice as well: one
that has bind_mouse_buttons, which says what a port's mouse is holding.
udev had it. Cocoa gets it (cocoa_bind_mouse_buttons(): the one mouse,
for the port whose Mouse Index is 0 - the rule its lightgun buttons
went by; left, right and the wheel, which is what it keeps). The two
drivers' own code for the buttons, and the helper each read a mouse
button with, are gone: 150 lines.

What changes besides:

- On a udev touch device the buttons went to the touch code, which
  answers none of them. They read their binds now.
- Cocoa's RetroPad binds on a mouse button are read: with
  bind_mouse_buttons the frontend has the mouse to read them from.
- The web and UWP drivers have bind_mouse_buttons and had no lightgun
  buttons at all. Theirs read their binds now too.

A lane in the frontend harness, with a driver that keeps its mice: the
trigger on a key, reload on the right mouse button, an auxiliary
button on a pad's button - each reads when its own is held and not
otherwise, and the driver is never asked.

Compiled for Linux (udev), macOS (Cocoa), Windows and Android. Not run
on a udev device or a Mac: the lane is what runs the frontend's side.

Why now: no driver may read a bind set it is handed before the
configured binds can lose the hotkey rows of users 2 to 16 (plan,
section 21, step 3). After this two do: PS4 and QNX.
2026-10-07 12:10:54 +02:00
LibretroAdmin f11d7ebcb6 video_thread: lend upload memory under the wrapper
A streamed surface under threaded video now writes its frames where
the driver uploads from, as it does under direct video. An update node
asks for the texture's upload memory (the same-numbered driver slot
for a surface of two, both for a surface of one); the video thread
lends it after the update, on the driver's own thread, and the node
brings it back to the surface. Readiness is the video thread's too: it
marks a lent slot busy when an update leaves the GPU reading it and
clears it once the driver says the read is done, and the main thread
asks video_thread_lend_ready, which reads one atomic and never waits.
The records go with their texture's unload and with the driver.

The surface lend lane runs threaded passes with two slots and with
one, and holds a driver that lends to lending under threaded video.
2026-10-07 12:10:17 +02:00
LibretroAdmin e73ded9c34 input: the test driver hands its keys to the frontend
The test input driver worked the RetroPad's buttons and the hotkeys out
itself, from the binds it was handed. It gives the frontend its keys
instead (test_keys_down()), as sixteen other drivers do, and reads no
bind.

Its answer for the whole pad at once was wrong besides: for each
button it looked at the right bind, then asked whether the key of the
bind at the number that asks for the whole pad (256) was down - a read
far past the end of a set of 96.

Run with a step file pressing A, then Right, then B and Start
together: a core reading the pad button by button and one reading it
whole both see 0100, 0080, 0009 and nothing between.

And the Wii U driver no longer tests whether a port's set is NULL: it
is an array, and never is.

Why now: the mapping compiler's third step drops the hotkey rows of
users 2 to 16 from the configured binds - the configuration loads
hotkeys for the first user only. That can only be done once no driver
reads a set it is handed, since drivers are asked for a hotkey by its
number. Six did. After this, four: PS4 and QNX, and the lightgun's
buttons in udev and Cocoa.
2026-10-07 11:48:19 +02:00
LibretroAdmin 7ae9f7848a input: a bind is eight bytes, and carries only what was chosen for it
A bind was a record of 20 bytes: its axis, pad button, key and mouse
button, and beside them its label, its number and two defaults nothing
read. Two tables hold a set of 96 for each of 16 users, 61,440 bytes,
and whole sets are copied on every remap and autoconfig apply.

struct retro_keybind is now the axis, the pad button, and sixteen bits
for the key (nine), the mouse button (six, all set for none) and
whether the bind is usable: eight bytes, 24,576 for the two tables.

What it no longer carries is asked of its place:

- the default tables in config.def.keybinds.h keep their layout and
  become struct retro_keybind_def; they are constant, and are where a
  bind's label comes from (input_config_bind_label()) and what it is
  reset to (input_config_bind_def(), input_config_bind_from_def()).
  Only input_driver.c includes them now.
- a bind's number is its place (input_config_bind_id()).
- the mouse button is read and written through RETRO_KEYBIND_MBUTTON()
  and RETRO_KEYBIND_SET_MBUTTON(). None is a value past every button,
  so a reader that looks the button up finds nothing, as for NO_BTN.

Found on the way: the default table for users 2 to 16 is three
entries short - it leaves three hotkeys out - and was copied in by
place. For those users every hotkey after them sat three places early
with another hotkey's label and number, and the last three places kept
zeroes, which read as button 0 and axis 0. Defaults are now put in by
number, and a bind that table does not have starts as nothing.

Measured, X11 with no controller, the frame's input work over 300
frames: 4,169,825 instructions before, 4,125,505 after.

Checked: the X11 session's log is the same as before; a configuration
with a key, a pad button, a hat, an axis, a mouse button and a wheel
bind for two users loads and saves the same; the frontend harness
passes every lane; winraw_keyboards_test passes under Wine. Compiled
for Windows, Android and macOS, with overlays and networking off, and
the Qt widget file against Qt 5.

The second step of the mapping compiler's order of work (plan, section
21). Left: struct retro_keybind_def still has its two unused fields,
which only the table's 383 initializers hold in place.
2026-10-07 11:41:32 +02:00
LibretroAdmin 8d7b49d1f7 rh264, rh265: no slot lock - without pointer atomics a decoder works alone
Pictures in flight on a pool hand their buffers over through slots of
one atomic swap. A backend without pointer atomics now gets no pool:
rh264_video_set_thread_pool and rh265_video_set_thread_pool decline it
there and the decoder works on the thread that calls it, so its slots
are plain and need no lock. rh264's pool of released planes, which
decoders on any thread share, is kept only where its slots are atomic;
elsewhere a released plane is freed.

Both lossless oracles gain a build on the volatile atomic backend with
threads on: every case decodes byte-exact with a pool offered, and the
H.264 one counts the pictures posted to it, which must be none.
2026-10-07 11:26:39 +02:00
LibretroAdmin be807cab55 input: no driver names the bind tables
The menu, the configuration and the UI get at a bind through
input_config_bind() and input_autoconf_bind(); the input drivers were
left free to name the two tables, and three did: Android (the Back
button's check of the profile's menu toggle), udev and Cocoa (a remapped
key's pad binds). Nine reads, all through the calls now.

tools/input_state_grab_check.py held the rule for everything outside
input/. It holds it for input/ too: input_driver.c and the header that
declares the tables are the only files that may name them, with a case
for that in its self-test. It is how the Cocoa driver was found - it
was not on the list of drivers thought to be left.

No change in behaviour: the calls are inline and return the same
address. This is the first step of the mapping compiler's order of
work (plan revision 1.99, section 21): after it how the binds are kept
can change without a driver changing with it.

Compiled for Linux (udev), Android and macOS (Cocoa).
2026-10-07 11:10:35 +02:00
LibretroAdmin 3a0c6ca842 task_overlay: a pack's images decode together, across the cores
Where the task queue runs on a thread of its own, every distinct image
an overlay pack names is decoded at once through
image_texture_set_run_ex before the first overlay is parsed, and each
item takes its image from there instead of decoding it. The decoded
pack is the same; while it decodes, each thread holds the file it is
reading, so the load peaks by up to one image file per extra thread.
An unthreaded queue, a single core and a pack in an archive keep the
item-by-item load paced on the frame.

The overlay budget sample loads a pack of six images named sixteen
times over, unthreaded and threaded: the same overlays and descs
either way, each image decoded once, and the threaded load decoded on
more than one thread.
2026-10-07 11:08:44 +02:00
libretroadmin e3c2ca68d9 font: text layout and measurement decode ASCII without a call
Every character the shared layout and measurement walk went through a
call to utf8_walk(), which for ASCII text was much of what a character
cost them. utf8_walk_inline() in encodings/utf.h takes the ASCII case
inline and calls utf8_walk() for anything else, with the same result.
Measuring a menu line takes 2.8 ns a character instead of 4.6.
2026-10-07 10:57:32 +02:00
LibretroAdmin be63f81780 video_thread: a present that waits and misses its vblank once does not miss it for good
With display pacing the core is held so its frame reaches the display
just before a vblank. For a driver whose present waits for the vblank
(glcore and gl with a swap interval) one frame arriving a moment late
was not one late frame:

- the video thread sits in that frame's swap until the next vblank;
- the core's next frame arrives meanwhile, on time, and is drawn only
  when the swap comes back - just after the vblank it was meant for;
- so it misses too, and so does every frame after it. Each on time,
  each a period late on screen, and nothing ends it.

The statistics could not show it either: a frame a period and a
quarter old is under the period and a half that asks for a drain.

threaded_video_test's model of such a driver, on a one-CPU machine:
every frame of 120 missed, 17 to 21 ms from core to display at 60 Hz.

Two things, both learned from the wait the driver reports
(video_thread_margin_learn()):

- the second miss in a row asks the hold to skip one period, as a
  queue a frame deep does (drain_pending). The frames after are back
  on their own vblank.
- the first miss of a run adds a millisecond to the hold's margin,
  which was an eighth of the drawing time and half a millisecond at
  least - not always there to be had; and while nothing misses, the
  least any frame had to spare over two seconds gives back half of
  what is over a millisecond. Never over a quarter of a period.

Only while display pacing is steering frames, and only for a driver
that says its frame is out when the present returns: a present that
queues, or does not wait, is left as it was.

The same model after: 0, 0 and 8 frames of 120 missed over three
runs, 1.4 to 3.4 ms from core to display, with 1 to 3 ms learned on
top of the margin. The lane counts the misses now.

Not run on a Mac. The lock would show there in the Swap Wait line of
the statistics: a wait near a whole period.
2026-10-07 10:57:15 +02:00
LibretroAdmin 36b24b69e7 task_overlay: a pack image is read once, by its decode
image_texture_load_request_ex answers from the bytes it decoded
whether the file is an animated PNG. The overlay loader takes that
answer, so a still is read once and only a file the decode found
animated is read again for the bytes the pack composes its frames
from.

The overlay budget sample counts the pack's image opens through the
VFS: none beyond the decode for a still, one for an animation, and
the pack keeps the animation's bytes and nothing for the still. The
loader sample checks the answer on a still and an APNG file.
2026-10-07 10:56:45 +02:00
libretroadmin 957ecd7b7c font: a grown glyph cache's hash grows with it
The hash had 256 buckets whatever the cache's size, so a cache grown
to 1024 cells walked chains four cells long on average and one grown
to 4096 sixteen, on every glyph drawn. A growth now makes a map with a
bucket per cell and moves every cell in use into it; the first map is
still the one inside the cache, and one that cannot be allocated
leaves the old map in place. The hash keeps a run of codepoints in
distinct buckets at every size.

A lookup in a full cache of 3800 glyphs takes about 4 ns instead of
18, of 900 about 3.6 instead of 5.5; in a cache that has not grown it
is unchanged.
2026-10-07 10:30:07 +02:00
LibretroAdmin ab4ce8925d companion_thumbs: a decode worker hands back its read pool as it ends
The still decodes read through data_transfer, whose pool of small-file
reservations is per thread. A worker now flushes it as it leaves, so
closing the companion leaves none of its workers' pools resident.

The handoff sample notes which threads decoded and which flushed, and
holds every decoding worker to a flush by the time the instance is
freed.
2026-10-07 10:28:32 +02:00
LibretroAdmin 722a714586 statistics: the wait in the driver's present has a line
Swap Wait:   3.21 ms for the display

How long the driver's present waits for the display, where the driver
says (glcore, gl, Vulkan), averaged as the render time is. The render
figure on the Core Start line no longer counts that time, and this is
where it went - and where a frame's lateness shows: a wait of most of
a period is a frame that missed its vblank, or a queue.

Carried in the presenter's statistics snapshot
(video_thread_present_wait_stats()); the blocking-present lane of
threaded_video_test checks it is there. Shown with threaded video,
with display pacing or without, and only where a driver reports a
wait.
2026-10-07 10:21:03 +02:00
LibretroAdmin dee741905d gl, vulkan: the present's wait is reported, as glcore's is
The threaded presenter holds the core back by a frame's drawing time
before each vblank, and took the whole frame call for that: a present
that waits for the display was counted as drawing. glcore says how
long its swap waited (get_last_present_wait); with it, input age on a
Mac under display pacing went from 22 ms to between 4 and 7.

- gl says the same, in the same words: the swap's time while a swap
  interval is set, and that the frame is out when it returns.
- Vulkan says how long its present and the taking of the next image
  took. Where the swapchain makes the driver wait for an image - on
  Metal's layer under MoltenVK, or with two images anywhere - that is
  waiting, and it is taken off the drawing time. It does not say the
  frame is out: with a queue that depends on how deep the queue was,
  which is not known here, so the frame is counted to the next vblank
  as before.

Nothing changes for a present that does not wait: the time reported
is then a few microseconds of call.

Not run on a Mac, where Vulkan still read 22 ms: whether this moves
it depends on where MoltenVK does its waiting, which the statistics
line added next is there to show.
2026-10-07 10:21:00 +02:00
LibretroAdmin ad812a7098 gfx_thumbnail: a frame's stills decode together, across the cores
Thumbnails asked for during a frame wait for its end and then go to
the task queue as sets as wide as the cores: one task decodes a set's
files at once through image_texture_set_run, each to the same request,
resampling and APNG verdict the single image task gives, and hands
them back in order. A set of one, a video, and an unthreaded task
queue keep the single task; a cancel before the frame's end sends
nothing. The menu drivers flush at the end of render.

The set's own threads hand back their data_transfer pools as they
exit (image_texture_set_run_ex), so neither a context reset's icon
set nor a thumbnail set leaves a read pool resident behind it.
2026-10-07 10:16:49 +02:00
libretroadmin 71856e644f local_samba_test: stopping samba waits for every daemon it spawned
smbd and samba-dcerpcd each run in their own session, and notifyd,
cleanupd and the rpcd workers in those sessions rename themselves or
outlive their leader for a moment, still writing tdb and socket files
into the run directory. Matching only the leaders by command line and
removing the temporary directory right away raced them, so a passing
matrix could fail on "rm: Directory not empty" in the exit trap.

Stop now signals every process in the sessions led by this config's
daemons and waits for them to exit, escalating to SIGKILL, before
anything is restarted or removed.
2026-10-07 10:15:35 +02:00
libretroadmin 1e112c4f9c font: a glyph cache miss takes its cell without searching them all
Every miss searched every cell for the least recently used one not
looked up in this frame; a grown cache has a thousand or more, and in
a frame drawing many glyphs new to it that search was most of the
time spent. A search now keeps the oldest 32 cells it saw, and a later
miss in the same frame takes the first of them that has not been used
since, which is the one the search would find: every other cell was
younger or excluded, and lookups only make cells younger. A frame in
which a search found every cell used gives the rest of its misses no
cell without searching again. The cell taken is the same as before.

A frame of 1500 lookups over 5000 codepoints takes about a quarter of
the time. The lookup of a cached glyph is unchanged. font_frame_test
checks the order cells are taken in, across more misses than one
search keeps and with hits between them.
2026-10-07 10:14:54 +02:00
LibretroAdmin 20076ff858 input: the hotkey pass is given the port's device, and does not ask
input_keys_pressed() runs for every active port every frame, and each
time asked input_config_get_device() what is plugged into the port's
core side: a call, which fetches the settings, checks them and the
port, and reads one word. Its caller has the settings in hand and the
port in range; it passes the word.

Instructions in the frame's input with a core that makes an ordinary
core's reads, callgrind over 300 frames, no controller: 4193016
before, 4169825 now - 77 fewer a frame.

What the function is given is what it asked for:
settings->uints.input_libretro_device[port], for a port below the
user count.
2026-10-07 10:09:02 +02:00
LibretroAdmin 728cad4796 input: a hotkey held through a change of window is one press
F toggles fullscreen. Held a little longer than the change takes, it
toggled again, and again: under a test X server, where the change
takes some 50 ms, F held for 150 ms went to fullscreen and back, and
held for a second it toggled eight times. On a desktop the change is
slower and it shows as the occasional double toggle.

Two things made it. Going to fullscreen and back restyles the window
or replaces it, and either way it is without the focus for a moment;
the X11 input driver reads no keys without the focus, so the poll
after the change gives F as up and the one after that as down again -
a press. And nothing remembered the first press. Input waits to be
let go at such moments (INP_FLAG_WAIT_INPUT_RELEASE), and a hotkey
found down during the wait is passed over; but the wait ends when the
RetroPad's buttons are up, whatever the hotkeys are doing, and a
hotkey still down then was taken as newly pressed.

- The wait goes on while a hotkey is found down under it, as well as
  while the RetroPad's buttons are: everything is let go before input
  counts again, which is what the wait is for. Not for the hotkey
  enabler, which is held to reach the others. The mark is kept in the
  hold's own byte, which the wait is already armed from each frame.
- After a fullscreen toggle and after a video driver restart the wait
  is kept up until the window has been found focused on three checks
  running, or for half a second where it is not going to be
  (input_driver_hold_over_window_change()): long enough for the key to
  be read as down again. What the core reads is not touched - a
  direction held through the change is still held after it.

X11 under a test server, F held for 0.08, 0.15, 0.4 and 1.0 s: 1, 2,
3 and 8 toggles before; one each now. Pressed twice 0.7 s apart: two,
before and now. With Game Focus on F still does nothing.

The frame's input work with no controller, callgrind over 300 frames:
4193016 before and 4193016 now. Nothing is added to a frame on which
no hold is active: the two values the wait after a window change
needs are kept out of the input state, where they moved what the poll
clears every frame, and the mark needs no test of its own.

The frontend harness gains a lane for the hold itself: a key held
through the wait does not count, a press after it was let go does,
and the hotkey enabler is not held back.

By reading, this comes of the X11 driver's own way with the focus and
is older than the input driver being kept across restarts; an old
tree was not built to see it.
2026-10-07 10:08:58 +02:00
LibretroAdmin 04d8870275 apple: the PowerPC build links again, and builds without warnings
retro_atomic.h's C11, C++11 and GCC __atomic backends set
RETRO_ATOMIC_HAS_64 on every target, but where a target has no 8-byte
atomic instructions (32-bit PowerPC, MIPS32 - the compiler reports
8-byte atomics as not always lock-free) each 64-bit operation compiles
to a libatomic call, which Darwin does not have: cocoa_common.m's
vblank timestamp left the PowerPC link with __atomic_load_8 undefined.
There the 64-bit operations are now left out, as on 32-bit __sync
targets, and their callers take the fallback they already have.

rcheevos' millisecond clock divided by CLOCKS_PER_SEC / 1000, which is
zero below a 10.5 deployment target, where Darwin's CLOCKS_PER_SEC is
CLK_TCK (100); and clock() is CPU time in any case.  Darwin builds
below macOS 10.12 / iOS 10, which have no clock_gettime, count with
mach_absolute_time() instead.

GCC 14's other warnings on that build, none a change in behaviour:

- rmp3: the short-block scalefactor loop is bounded as i + 2 <
  n_short_sfb (the same iterations, n_short_sfb being a multiple of
  3) under a test that n_long_sfb + n_short_sfb fits iscf, which
  every block the side info can describe does.  Decoded output is
  bit-identical on MPEG-1 and MPEG-2 streams with short blocks.
- vp9: the d135 predictor says its block is at most 32 wide.
- The pointer frame's read_as index is tested against MAX_USERS as
  well as against the count it is clamped to.
- The scanner's serial query is copied together from pieces of known
  length rather than through a snprintf the compiler cannot bound.
- The companion spells 'fldr' as its value and reads the name
  column's font through an NSCell.
2026-10-07 09:54:48 +02:00
libretroadmin 30d65c9143 sdl, xvideo, oga: software text leaves untouched what it does not change
SDL's blend and XVideo's two YUV ones read, blended and wrote back
every texel of a glyph's cell, clear ones included, though a clear
texel's blend returns the pixel as it was. They pass over clear texels
(XVideo, clear pairs), the result unchanged; XVideo also writes a
lone last texel's luma only when it has coverage, and loads the font
colour and byte offsets once a glyph.

OGA copied a glyph row by row up to its advance but stepped its source
on by its width, so a glyph wider than its advance - every stb glyph,
whose width is the cell's - was drawn sheared row by row. Each row now
starts at its own atlas row, and the copy has no per-texel test.
2026-10-07 09:53:38 +02:00
libretroadmin cfa2e57d58 font: a glyph cache hit takes no stack frame
The miss path was inlined into the lookup, so every hit - nearly every
glyph drawn - saved and restored the six registers and the stack frame
a miss needs. With the miss kept out of line a hit is the hash, the
chain walk and the two usage stores; lookups take about a third less
time. The frame epoch read there is already a plain load on every
backend and stays as it is.
2026-10-07 09:53:35 +02:00
LibretroAdmin e5fc0a50e4 apple: the App Store builds compile without warnings, and their uploads stop failing on one bad core
Built the way Xcode builds them - the App Store floors (macOS 12,
iOS/tvOS 15), the projects' warnings, every translation unit - the
tree still warned:

- Delegate methods the SDK deprecates that an older OS still calls
  are no longer implemented in source.  apple_rt_add_method() gives
  the class each one at run time, from +initialize and only on the
  releases that call it: -locationManager:didChangeAuthorizationStatus:
  before macOS 11 / iOS 14 (-locationManagerDidChangeAuthorization:
  from there), -documentPicker:didPickDocumentAtURL: before iOS 11
  (the URL-array method from there), -shouldAutorotateTo-
  InterfaceOrientation: before iOS 6, and NSView's
  -layer:shouldInheritContentsScale:fromWindow: from 10.7.
  class_addMethod is looked up by name, as the 10.4 binary may not
  import it.  CocoaView no longer declares UIDocumentPickerDelegate,
  whose pre-11 SDKs require the method it now adds at run time, and
  -prefersPointerLocked is not built for tvOS, where it is
  unavailable.
- The open panel's file types, the companion's folder icon and the
  cores folder's open are -setAllowedContentTypes:, -iconForContent-
  Type: and -openURL: from macOS 11, the 10.x methods before it, by
  selector where they are not on every release.
- The alert's end selector is looked up by name, and on SDKs older
  than the selectors ui_cocoa.m names (10.6, 10.7, 10.9) they are
  declared in an unimplemented NSObject category, so @selector()
  stays a constant.
- size_t and 64-bit values narrowed without a cast in configuration.c,
  gl3.c, cdrom.c, media_detect_cd.c and vfs_implementation_cdrom.c;
  rchd_read_extent() rejects an extent past the last 32-bit frame
  instead of wrapping into the wrong track.

App Store uploads: one iOS or tvOS core without an unencrypted
LC_ENCRYPTION_INFO_64 - written by Apple's ld, not by other linkers -
fails the whole upload (ITMS-90125).  make-frameworks.sh leaves such a
core out of an App Store build with a warning naming it.  The
Fastfile's check for a build already in TestFlight review passed
Build.all a filter it does not take, so it always failed and the
upload went on to external distribution and stopped there; it asks
App Store Connect for the newest five builds of the platform with
their review submissions instead.
2026-10-07 09:38:29 +02:00
LibretroAdmin bf1f5a4a70 gfx_surface: stills decode through the loader's request and set
A still's decode asks the loader for what the driver takes - 10-bit
where it samples it, the compressed payload as it lies in the file -
through image_texture_load_request and its buffer twin, rather than
an image_texture_load that only ever produced 8-bit. A set of files
decodes through image_texture_load_set, the parallel decode moved to
libretro-common as formats/image_texture_set.c: dispatch_apply on
Apple, threads of its own elsewhere, in order on one, and
image_texture_set_run for any other per-index work.

The loader sample gains the set lanes: files decoded together with
missing, empty and NULL paths left empty, and every index of a run
called exactly once.
2026-10-07 09:34:25 +02:00
libretroadmin 9773b6956b gl1, gl2: the font texture takes only the atlas region that changed
An upload converted and sent every row the dirty region touched, the
texture's full width, from a buffer allocated and zeroed for it. Only
the dirty region goes now, widened to whole groups of four texels: its
rows are a multiple of eight bytes, so it uploads the same under any
GL_UNPACK_ALIGNMENT, and are packed tightly in the buffer it is
converted into, which needs no GL_UNPACK_ROW_LENGTH on OpenGL ES 2.
Texels past the atlas are still written clear.
2026-10-07 09:19:09 +02:00
libretroadmin 5b566383e8 vulkan, metal: a glyph lookup sends the atlas's dirty cells
A lookup that changed the atlas copied the glyph's own rectangle into
the staging texture (Vulkan) or the buffer the texture is made on
(Metal). A glyph's size can be smaller than its cell - FreeType's is
its bitmap's - so the texels around it kept what the cell's earlier
glyph left there, one texel from the edge linear filtering samples.
The atlas's dirty region, whole cells, is copied instead.

Vulkan's region still to send to the sampled texture is kept as two
packed corners, merged with VIDEO_SCALE_MIN/MAX.
2026-10-07 09:19:06 +02:00
LibretroAdmin ebecde90c9 threaded_video_test: the blocking-present lane checks the accounting
The lane added with get_last_present_wait failed on CI:

   blocking present: a frame that went out on its vblank reads
   16.59 ms to it

It asserted where the frames landed - a latency under 9 ms - and that
is the machine's doing, not the presenter's. The hold aims a frame
half a millisecond ahead of its vblank; where the video thread is not
woken within that, the frame arrives after the vblank and its swap
waits for the next, a whole period. On the CI runner about a quarter
of the frames did (the swap waited 3.83 ms on average), and on the
box the lane was written on, on a later run, most did.

What the commit changed is the accounting, and that is what the lane
checks now, frame by frame and whichever vblank a frame met:
- the frame is counted to the vblank its swap came back on, not the
  one after (8 single frames, 6 to be right, for a thread put to sleep
  between the swap and the stamp);
- the wait is not drawing time, however long it was.
The driver that says nothing of its wait is still run first, and what
it reads is printed: counted 16.67 ms past the swap's vblank.

Run here with most frames missing their vblank (the swap waited
12.13 ms on average): 8 of 8 counted to it, render 0.00 ms.

The lane is right to have been uneasy, though. With a present that
waits, a frame that misses its vblank costs a period, and half a
millisecond is all the hold leaves: that margin wants looking at for
such drivers, where the wait itself says how much was left.
2026-10-07 09:02:55 +02:00
LibretroAdmin 4d8fa8fff9 image_texture: one still loader, stepped or run to completion
The still decode that tasks/task_image.c kept as its own state machine
- a decode against a buffer still being read, in steps that stop at a
time budget or at the byte frontier - is libretro-common's
image_loader: open on a buffer and how much of it is resident, step
against a deadline the caller's clock reports (or untimed), finish
into a texture_image. The request it takes names what the caller
wants of the still: channel order, 10-bit, half floats, a compressed
payload. image_texture_load() is the loader run to completion;
task_image is the retro_task over it, keeping the read spine, the
slice window and the driver's answers to the request.

The scaling pass moves with it, as image_texture_scale() in a unit of
its own so the scaler comes only to those who call it. A step never
runs a transfer's last iteration and the first pixel pass together,
so a tick costs what it did.

libretro-common/samples/formats/image_loader drives the loader whole,
timed, over a growing prefix, aborted, for the APNG verdict and the
request's answers, and the scale pass.
2026-10-07 08:56:31 +02:00
LibretroAdmin 4c16d2bc3f gl, glcore: the thread that waits on the ring's fence does not delete it
Three crash logs with a hardware rendered core on glcore, NVIDIA on
Windows, end in the same place: the driver aborting inside the
glDeleteSync the core's thread makes once its wait on the video
thread's fence has returned. Since cf12b75b that fence is made just
ahead of the swap, so the delete can land while the video thread is
still inside that swap.

The waiter now only waits. The sync stays in its cell and the video
thread, which made it, deletes it when it signals the cell again or
frees the ring, as it already did for one left behind.

tools/gl_ring_fence_check.py fails if either driver's wait deletes.
2026-10-07 08:56:05 +02:00
libretroadmin b6391a96d6 freetype: render a glyph straight into its atlas cell
FreeType from 2.10 places and sizes a glyph's bitmap when it loads the
glyph, so the outline is rendered into the cell itself, at that place,
instead of into a bitmap FreeType allocates and that is then copied in
row by row. A16 coverage is rendered as 8 bits at the start of each
row and widened in place. Overlapping contours, which FT_Render_Glyph
renders oversampled, and a glyph larger than the cell, whose clipped
edge row the rasterizer rounds differently, still take the copy, as
does every glyph before 2.10. Output is unchanged byte for byte.

A rasterizer that cannot draw a glyph may leave its cell changed; the
cache draws nothing from that cell until it holds another glyph.
2026-10-07 08:27:34 +02:00
libretroadmin 551fe59e68 font: a glyph's size and atlas offset are packed pairs
struct font_glyph carries its width and height as dims and the texel
offset of its top-left pixel as atlas_pos, each in VIDEO_SCALE_PACK's
layout. The glyph cache hands a cell's packed origin straight to the
dirty region, and stb and CoreText store the cell they drew into as
the glyph's size as it came. The drivers read the halves through
VIDEO_SCALE_W/H; OpenVG's child-image cache keys on the two words.
2026-10-07 08:27:30 +02:00
LibretroAdmin cfc7e71eae gfx_thumbnail: the decode worker is lock-free or absent
The animated-preview worker needs the atomic pointer ops for its
queue. A build whose atomic backend has none - the volatile fallback,
which no threaded target in the tree selects - decodes in line, as a
build without threads does, rather than through a second, lock-backed
worker kept only for that configuration.
2026-10-07 07:58:36 +02:00
libretroadmin 29dabf0da5 font: a glyph cache's unused cells are older than any in use
A new cache's cells started out with the same age as the first glyph
rasterized into it, so the first glyph new to the font in a later frame
evicted the space from cell 0 while most of the atlas sat empty - to be
rasterized again the next time a space was drawn, and pulling the dirty
region out to the atlas's corner. The cells are marked older than any
in use, as the ones a growth adds already are.

font_frame_test checks that a new glyph takes an empty cell and every
glyph the font was made with stays cached.
2026-10-07 07:56:34 +02:00
LibretroAdmin 49846862ba apple: the iOS 6 build compiles clean, and CI holds it there
RetroArch_iOS6.xcodeproj builds for armv7 at a 6.0 deployment target
against the iOS 9.3 SDK, and everything newer than iOS 6 that it
called directly is now behind a runtime check:

- CADisplayLink's rate goes through cocoa_display_link_set_rate() and
  cocoa_display_link_get_rate(): the frame-rate range from iOS 15,
  preferredFramesPerSecond from iOS 10, frameInterval before that.
  maximumFramesPerSecond, nativeBounds and safeAreaInsets are selector
  sends behind their versions.
- The document picker, size transitions and openRetroArchURL: are
  annotated API_AVAILABLE and reached through iOS 8 checks; the
  library reply opens through openURL:options:completionHandler: from
  iOS 10 and openURL: before it.
- UISelectionFeedbackGenerator, the alternate icons, layout anchors,
  the smart-text traits and base64 encoding go by selector and class
  lookup; containsString: gives way to rangeOfString:.
- MetricKit, scenes, pointer interactions and CoreHaptics declare types
  the 9.3 SDK does not have, so those stay behind SDK gates.

apple_runtime.h supplies API_AVAILABLE on SDKs older than iOS 11 as
clang's availability attribute, so annotated methods are checked there
too.  verbosity.c includes os/log.h on the same condition as its call,
gl2.c's macOS FBO extension path no longer takes iOS SDKs that also
define the macOS version macro, mem_stats.c reads
limit_bytes_remaining only where task_vm_info has it, and PRI_SIZET is
"lu" on Apple.

OpenAL is deprecated from macOS 10.15 / iOS 13 but present on every
release; openal.c defines the headers' marker empty, as the GL headers
take GL_SILENCE_DEPRECATION.  features_cpu.c declares the affinity
array only where it is filled, and the tvOS display server no longer
keeps a screen it does not use.

The new "CI iOS 6 (armv7)" workflow fetches only the 9.3 SDK's headers
and runs .github/workflows/scripts/ios-legacy-check.sh, which compiles
griffin_objc.m (ARC) and griffin.c with the iOS 6 project's own
defines at a 6.0 floor and treats every warning as an error, so an
unguarded newer API fails the job.
2026-10-07 07:41:37 +02:00
libretroadmin 76d678fc4c sdl2: the OSD font texture takes only the atlas region that changed
The OSD font's texture is an ARGB one made at the atlas's size and fed
from an ARGB copy of the atlas, instead of being made again from a
palettized, colour-keyed surface every time a glyph is new to the
atlas. An upload converts and sends only the atlas's dirty region. A
texel is the coverage as grey, opaque where there is any and clear
where there is none - what the colour key produced - so the additive
draw lights the same pixels by the same amounts.

font_sdl2_test checks the OSD texture against the atlas after two
batches of new glyphs, every channel, as it does the raster font's.
2026-10-07 07:40:37 +02:00
LibretroAdmin 0df885084a gfx_thumbnail: the still is a gfx_surface still
A thumbnail's still goes through gfx_surface_submit_image like a menu
icon's: the surface takes the decoded image, keeps the texture up until
the next one has landed, and frees the image whether or not the
thumbnail is still there when it does. The thumbnail no longer owns a
texture of its own, so the flag that told a surface's texture from a
still's goes, with the manual unloads behind it.

A still accepts 10-bit and half-float images on the queued path: a
10-bit image for a driver that cannot sample it is narrowed where it
is the surface's to rewrite, a half-float one for such a driver is
refused rather than uploaded as another layout. A still may carry a
release callback for a caller that has to know when an image landed.

The anim harness drives the real still path: queued under threaded
video with the status pending until delivery, a second request while
the first is on its way, a reset with one on its way, direct video,
and refused half floats.
2026-10-07 07:33:41 +02:00
LibretroAdmin c64e58389b video_thread: a present that waits for the vblank is not drawing time
The threaded presenter times a driver's frame call to learn how long
a frame takes to draw, holds the core back by that much before each
vblank, and takes the frame to go out on the first vblank after the
call returns.

An OpenGL swap with a swap interval set does not return until the
vblank has passed and the frame is out. Both readings were then
wrong. The wait was counted as drawing, so the hold started the core
that much earlier, the wait grew by as much, and so on until the
presenter read the frame as queued behind another and drained; and
the frame was counted to the vblank after the one it went out on. A
frame handed over a millisecond before its vblank, and shown on it,
read as some 21 ms - which is what glcore shows on a Mac with display
pacing on, now that the display link gives it the display's vblanks.

A driver can say how long its present waited and whether the frame is
out as the call returns (get_last_present_wait, last in the poke
interface so that no other table changes). The wait is taken off the
render time, and a frame that is out is measured to when it went out.
glcore says so for its swap while a swap interval is set.

samples/gfx/threaded_video gains a lane: a driver whose frame call
waits for the next vblank of a 60 Hz display, first saying nothing of
it and then saying so. Saying nothing, the latency reads 20.93 ms and
the render time 4.24 ms; saying so, 0.37 ms and 0.00 ms, with the swap
waiting 0.34 ms on average.

Not done here: the gl driver and Vulkan, which do not report a wait
yet.
2026-10-07 07:32:43 +02:00
libretroadmin 0208eb8a39 sdl2, sdl3: the font texture takes only the atlas region that changed
The raster font keeps its texture and RGBA staging copy until the atlas
grows. An upload then converts and sends only the atlas's dirty region,
or all of it for a texture just made, instead of expanding the whole
atlas - and on SDL2 allocating a buffer and remaking the texture -
every time a glyph is new to it.

font_sdl2_test draws the SDL2 raster font's texture back after two
batches of new glyphs and compares it with the atlas texel for texel.
2026-10-07 07:24:17 +02:00
libretroadmin 89d12be3ff stb: the rasterizer keeps its working memory with the face
A glyph no longer allocates its accumulation cells and outline scratch
and clears all of them: the face holds that memory, grown to the
largest cell asked of it, and each glyph leaves it zeroed for the next
by clearing only the cells its edges reached - every row's are those
before its extent. A cell of another size clears the layout once. The
glyph's bounding box is read once and handed to the rasterizer.

Output is unchanged byte for byte, in both coverage formats and for a
cell the face's own size, larger or smaller.
2026-10-07 07:24:13 +02:00
LibretroAdmin f8d5ba941d apple: objc runtime headers are imported; the companion builds on GNUstep again
The 10.5 and older SDKs' objc/message.h has no include guard and is
written to be imported, so apple_runtime.h and every file that also
pulls in the objc runtime headers #import them; a second plain
#include redefined struct objc_super and stopped the PowerPC build.

The companion spells the left-mouse event types and masks and the
rounded bezel through its own names, which are the older spellings
GNUstep's AppKit has and the current ones on Apple.  On the 10.5 SDK,
+sortDescriptorWithKey:ascending: (10.6) gives way to the 10.3
initialiser it wraps, and the 10.6 calls already behind
respondsToSelector: - -setStyleMask:, -setHelpMenu:, -setColorSpace:,
-[NSBundle URLForResource:withExtension:subdirectory:] - are sent by
selector.
2026-10-07 07:16:35 +02:00
libretroadmin 52aa4d1cf3 coretext: draw a glyph straight into its atlas cell
The bitmap context is made over the cell itself, with the atlas's row
pitch, so a glyph no longer goes through a buffer allocated, drawn and
copied row by row on every cache miss. The cell is cleared only once
every CoreFoundation object the draw needs has been made, so a glyph
that fails leaves it as it was.
2026-10-07 07:03:52 +02:00
libretroadmin 7d704b209e font: keep the atlas's coordinate and size pairs packed
The font atlas's dirty region is two corners and its growth limit one
size, each a pair in VIDEO_SCALE_PACK's layout: dirty_xy0/dirty_xy1 and
max_dims. The glyph cache grows the region without unpacking it - a
cell's origin plus its size adds as one word, since the atlas never
outgrows VIDEO_SCALE_DIM_MAX, and VIDEO_SCALE_MIN/MAX take each axis's
least or greatest with the other half masked off. The rasterizers are
handed the cell as the packed word it is kept in and split it only
where a row loop needs the halves.

Consumers read the corners through VIDEO_SCALE_W/H, and the drivers'
upload helpers take them packed. The pending copy region in d3d12 and
the size each driver keeps its font texture at (gl3, d3d9, rsx, sdl3, vg)
follow the same layout, as d3d8, gl1 and gl2 already did, which makes
the per-frame "has the atlas grown" test one comparison. font_frame_test
checks that the region is the per-axis bounding box of the cells drawn.
2026-10-07 07:03:48 +02:00
libretroadmin 687ad01746 ci: the Metal stub lane has objc/runtime.h and objc/message.h
apple_runtime.h sends messages through objc_msgSend and looks up
selectors and classes by name, so metal.m now includes the runtime's
own headers. The stub SDK carries stand-ins declaring just those entry
points, with the signatures clang knows them by, and the Metal lane in
compile-matrix.sh compiles again.
2026-10-07 07:03:43 +02:00
LibretroAdmin 9c36438fbe cocoa: the display's vblank, for the threaded presenter on macOS
With threaded video and display pacing on, a Mac at 60 Hz showed

   Latency:   ~17 ms to vblank (est.)
   Input age: ~20 ms poll to vblank

under Metal, glcore and Vulkan alike, against 2 ms on Windows.

"(est.)" is the reason. The presenter lays its vblanks from the time
the driver gives for its last present, and aims the core's start at
them. Given none, it takes the frame to go out a whole period after
the frame call returned: 16.7 ms and the render, which is the 17, and
the core's few milliseconds on top, which is the 20. That is not a
measurement, and it is not only the figure that is off: display
pacing with no display to go by paces to a grid of its own making,
and cannot place the core's start just ahead of the real vblank.

No driver on macOS had a time to give. The OpenGL and Vulkan contexts
had no such report (the Windows and X11 ones do), and Metal's comes
from a drawable's presented time, which evidently is not always
there.

The view's display link (macOS 14 and later) is called for every
vblank and carries the vblank's own time. It is kept
(cocoa_last_vblank_time()), bridged to the frontend's clock the way
the Metal driver bridges a presented time, and
- the Cocoa OpenGL and Vulkan contexts report it as their last
  present time;
- Metal reports it when no drawable has given one.
Before macOS 14 there is no such link here and nothing changes.

Compiled for macOS (arm64, the 14.5 SDK's headers, clang, with and
without ARC): cocoa_common.m, cocoa_gl_ctx.m, cocoa_vk_ctx.m and
metal.m, no errors and no warnings on the new lines. Not run: there
is no Mac here. What to look for on one: the Latency line without
"(est.)", and an input age of a few milliseconds with display pacing
on.

Not touched: with display pacing off the same Mac showed 66 ms, four
periods, which looks like frames queueing and is another matter.
2026-10-07 07:02:47 +02:00
LibretroAdmin 0b505edce3 glcore: the context's present time reaches the threaded presenter
The threaded presenter lays its vblanks from the time a driver gives
it for its last present (get_last_present_time), and paces the core to
them when display pacing is on. The gl driver passes on what its
window system context reports; glcore gave nothing, on every platform,
so under glcore the presenter guessed - a period after the frame call
returned - whatever the context could have told it.

It passes the context's report on, as gl does. Nothing changes where
the context has none.
2026-10-07 07:02:44 +02:00
LibretroAdmin 61a2bcbed7 apple: iOS and tvOS reach their newer APIs by selector too
The iOS and tvOS paths take the same treatment as macOS, down to an iOS
12 floor and with deprecations reported: the UIKit, GameController,
MetricKit, AVFoundation and CoreLocation calls above the floor go
through the apple_runtime.h sends behind their OS checks, enum values
newer than the floor are compared by value, and the display link's
frame-rate range is set and read by selector on every platform without
an SDK gate.  apple_runtime.h gains a 9-16 byte struct getter and
-init sends whose casts carry the init family's ownership.

Code that uses a framework throughout - the keypress and device
haptics, camera authorization, speech - moves into classes carrying
its availability, so it calls the framework directly, and is reached
by selector through a class looked up once.  Deprecated calls with a
successor on every floor take it; the rest are sent by selector.  The
iOS display link and GCDWebServer's lastModifiedDate declare their
ownership, and the iOS display link's rate read is guarded for systems
before iOS 10.3.
2026-10-07 06:55:20 +02:00
LibretroAdmin b05f954743 apple: the macOS build reaches newer APIs by selector too
Everything the macOS build calls above its deployment floor goes
through the apple_runtime.h sends behind its existing OS check: the
GameController physical input profile and gesture state, GCMotion's
activation and acceleration, the haptics rumbler's own macOS 11
methods, the CocoaView display link and its frame-rate range, the
location authorisation status, the PQ colour space name, BC texture
support and the screen's maximum frame rate.  Element names and
exported constants are looked up by symbol once, selectors on the poll
paths are cached, and a struct the ABI returns in memory goes through
objc_msgSend_stret everywhere but arm64.  memmap resolves
pthread_jit_write_protect_np at load.

Deprecated spellings with a same-valued successor take the successor,
mapped back in cocoa_defines.h for older SDKs; deprecated calls with
no successor on the floor are sent by selector, and the companion
converts window and screen coordinates by the frame origin.
2026-10-07 06:42:57 +02:00
LibretroAdmin 57591e8985 menu: the rest of a setting's commands that fired twice
#19730 made a row's command fire once where the row's flags ask for
it, and named what it left:

- A command the change handler picks itself - the audio rows, the
  video scale, HDR - is fired whatever the flags say, so hiding the
  flags while the row's handler runs did nothing for it. Start resets
  such a row and runs the change handler, and menu_setting_generic()
  runs it again: Start on Windowed Scale reinitialised the video
  driver twice. The change handlers now hold their command for the
  row whose handler is running (setting_cmd_held_for), and it fires
  from menu_setting_generic() alone, as the flagged ones do.
- A path picked for a setting (set_path_generic()) and text entered
  for one (menu_input_st_string_cb()) ran the change handler
  themselves and then called menu_setting_generic(), which runs it.
  The first call is gone.

samples/menu/setting_command gains a lane: Start on the video scale,
off its default, resets it and initialises the null video driver
once. With the hold taken out it fails, two times for one.

The path and text cases have no lane: both are behind a file browser
or the on-screen keyboard. The change there is the removal of a call
that the next line repeats.
2026-10-07 06:34:10 +02:00
LibretroAdmin 822e4b2f43 input: a held combination reports once for the hold
The two combinations that are a button held for two seconds - Hold
Start, Hold Select - started their two seconds again the frame after
they ran out. A button kept down reported every two seconds: with
"Confirm Quit" on, holding the quit combination for four seconds was
its own second press and quit, the fault #19711 fixed for the
combinations of several buttons and left as it was for these; and a
held menu combination asked for the menu again.

A hold that has reported does not start again until the button is let
go (combo_hold_reported, a bit a combination).

The frontend harness gains a lane for both fixes:
- with a clock of its own, Start held for ten seconds reports once,
  once more when let go and held again, and not at all when held for
  one second; L2+R2 is reported on every frame it is held, which is
  why the quit check counts the frame it starts;
- through the frontend, with Confirm Quit on and L2+R2 the quit
  combination, the two held for a second do not quit.
With this commit's change taken out the first fails ("Start held for
ten seconds did not report once"); with #19711's taken out, the
second.
2026-10-07 06:34:06 +02:00
LibretroAdmin b5fc28af0e gfx_widgets: three message queue icon fields nothing reads go 2026-10-07 06:33:41 +02:00
LibretroAdmin 3a80949adc apple: APIs above the deployment floor are sent by selector
One binary runs from the deployment floor up to the current OS, so an
API newer than the floor is called behind an apple_runtime_available()
check.  Written as a message expression that call trips
-Wunguarded-availability-new on every build, and fails to compile on
an SDK that does not declare it.  apple_runtime.h gains macros that
send through objc_msgSend with a selector looked up by name - the call
a message expression compiles to, so nothing is slower and nothing is
left to inlining - plus lookups for a class and an exported constant
by name.  The Metal EDR calls, the App Shortcuts bridge, the
orientation update, the scene geometry read on the sensor path
(selectors cached in statics) and the external camera type go through
them.

metal.m declares its HDR-only methods under METAL_HDR_AVAILABLE, so the
tvOS build no longer reports them as unimplemented, and the end-of-frame
composite call sits under the same gate.  sdl2_gfx counts atlas pixels
in size_t.
2026-10-07 06:18:05 +02:00
LibretroAdmin b79318bad9 apple/fastlane: the CI stream carries errors only
A GitLab job log is cut off at 4 MB and nothing after that is kept, so
a build whose warnings fill the stream loses the error that failed it.
xcbeautify prints only errors to the job; the complete xcodebuild log
still lands in buildlog/ on the runner.
2026-10-07 06:18:00 +02:00
LibretroAdmin 47474ed9f1 win32: a start with the power plan off loads powrprof only when a run left the copy behind
Turning the plan off at every configuration load, to undo a copy a run
that ended badly left active, loaded powrprof and asked for the active
scheme on every start. A run that activates the copy marks it in the
registry and the restore clears the mark; a start with the setting off
reads the mark and, with none, loads nothing.

The power plan harness gains the two starts: one with nothing left
behind, which loads powrprof not at all, and one with the mark set,
which restores and clears it.
2026-10-07 06:14:33 +02:00
Davey Hughes 2fb077f223 menu: a setting's command fires once per press (#19730)
menu_action_handle_setting() calls the row's handler and then
menu_setting_generic(), and both run the change handler. A handler
that runs it itself, as the boolean ones and Start do, fired the
row's command twice: once through SD_FLAG_CMD_APPLY_AUTO, or through
SD_FLAG_CMD_TRIGGER_EVENT_TRIGGERED, which stays set after a row's
first press, and once more from menu_setting_generic(). Toggling a
boolean with CMD_EVENT_REINIT, such as Bilinear Filtering from its
second press on, reinitialised the video driver twice.

The row carries neither flag while its handler runs, so the command
fires from menu_setting_generic() alone.

Still twice: set_path_generic() and menu_input_st_string_cb(), which
run the change handler before menu_setting_generic() themselves, and
the rows general_write_handler() picks the command for, such as the
audio ones.

samples/menu/setting_command presses two such rows on the null video
driver with OK, right, left and Start, and counts its inits.
2026-10-07 06:15:31 +02:00
Anthony SchemelandClaude Opus 5.5 5a45fd3598 runloop: the quit controller combo counts once per press (#19711)
input_driver_button_combo() is true on every frame the combo is held,
while the quit key is taken only on the frame it goes down.  With
"confirm_quit" on, the first frame of an L2+R2 press showed "Press again
to quit" and the next frame, a few milliseconds later, was taken as the
second press, so the combo quit at once.  The combo now counts only on
the frame it is first held, as the key does.  The hold combos report a
single frame already and are unchanged.

Fixes #19642.

Tested with the test joypad driver (null video, audio and input; quit
combo L2+R2; 2048 core): one press held for 60 frames no longer quits;
two presses quit on the second; with confirm_quit off one press quits.
runloop.c compiles with C89_BUILD=1.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-07 06:15:16 +02:00
LibretroAdmin 79da19d01c input: whether a hotkey is blocked is asked for the one that is pressed
"Hotkey Enable" blocks the hotkeys while it is not held. The rules
tell a controller's hotkeys from the keyboard's where only one of the
two has an enabler, with an exception for Game Focus, and they were
worked out for every hotkey - into a table of sixty - on every frame
the enabler was not held, for the hotkeys gone through afterwards to
look themselves up in. A hotkey looks itself up only when it is
pressed.

With the enabler on the controller only, the usual way to set it up,
working the table out is a keyboard read and a controller read for
each hotkey that has a key, every frame, to learn about hotkeys
nobody is pressing: some 7,300 instructions a frame, nearly half
again what the rest of the frame's input takes. With no enabler at
all the table was still cleared, sixty entries, every frame.

The rules are a function of the hotkey (input_hotkey_blocked()), in
the words they had, asked from the walk over the hotkeys when one is
found pressed. The enabler's own answer, which the Game Focus
exception wants for the others, is asked once and kept for the call.

Instructions in the frame's input with a core that makes an ordinary
core's reads, callgrind over 300 frames under X11:
- the scripted test controller with Hotkey Enable on one of its
  buttons: 7027719 before, 4834639 now, 31% fewer;
- no controller and no enabler: 4486347 before, 4193016 now.

A build that still worked the table out, every frame, and compared it
with the answer asked for each of the sixty hotkeys - once as things
were and once with the keyboard taken to be the core's, since the
test core takes no keyboard and Game Focus does not come on with it -
ran sessions with hotkeys pressed with and without the enabler held:
the enabler on the controller only, on the keyboard only, and on
both. 123074, 148854994 and 131688 answers, each the same as the
table's; the frontend harness on the same build, 74281.

This was the last of what stood before the mapping compiler that can
be run here: the rules now go by which hotkeys are down.
2026-10-07 06:01:57 +02:00
LibretroAdmin 824734388c gfx_surface: an icon set is decoded across the cores at a context reset
The theme and icon sets Ozone, XMB and MaterialUI load at a context
reset were a hundred PNG decodes one after another on the main thread,
whatever the video driver. gfx_surface_submit_files decodes a set of
files across the cores, thirty-two at a time - on Apple over the
dispatch pool the task queue's own decodes run on, elsewhere on threads
of its own - and gives each image to its still in slot order once they
are back, so the icons are there at the reset's end as before;
gfx_surface_submit_named makes the stills and the paths for a set named
one slot at a time.

The still sample decodes a set through a stub decoder that notes its
threads: all but the missing file go up, in order, on more than one
thread where there is more than one core.
2026-10-07 06:00:58 +02:00
LibretroAdmin 29816dd948 input/ps3: the frontend reads the keys bound to the RetroPad
As for Android, DOS and the Wii: ps3_input_keys_down() says which of
the keys the frontend asks about are down, and the driver no longer
walks the binds itself.

One thing changes. The driver's walk had no regard for the keyboard
being the core's: with Game Focus on, keys bound to the RetroPad still
pressed its buttons, which no other driver allowed. The frontend's
rule is the one every driver has.

Compiled with the PS3 toolchain's gcc 13.2 against the PSL1GHT
headers with the PSL1GHT Makefile's defines: the same messages before
and after (the file is built as part of one large file and does not
stand alone: two symbols from the keymap come up undeclared either
way). Not built with the Sony SDK, not run on a console.

That leaves no input driver evaluating key binds but the scripted
test driver.
2026-10-07 05:43:59 +02:00
LibretroAdmin de8b9db896 input/gx: the frontend reads the keys bound to the RetroPad
As for Android and DOS: on the Wii, with a USB keyboard,
rvl_input_keys_down() says which of the keys the frontend asks about
are down, and the driver no longer walks the binds itself. The
GameCube build has no keyboard and gives no keys, as before.

The driver's walk was also behind its test that the port's controller
index is one of its four; a keyboard's binds do not depend on which
controller a port has, and the test is not carried over.

Compiled with devkitPPC and libogc by the Wii Makefile's own command,
and as the GameCube build has it, no messages. Not run on a console.
2026-10-07 05:43:56 +02:00
LibretroAdmin c8fa93d622 input/dos: the frontend reads the keys bound to the RetroPad
As for Android in the commit before: dos_input_keys_down() says which
of the keys the frontend asks about are down, and the driver no longer
walks the binds itself.

The driver's walk indexed its key table with the keymap's value as it
stands, where its keyboard device masks it to the table's size; the
new function masks it the same way.

Compiled with DJGPP (gcc 12.2) by the DOS Makefile's own command, no
messages. Not run under DOS.
2026-10-07 05:43:52 +02:00
LibretroAdmin 6dda089a9f input/android: the frontend reads the keys bound to the RetroPad
The Android driver evaluated a port's key binds itself, for the
RetroPad mask and for each button. The frontend does that for every
driver that hands it the keys that are down (keys_down), with one set
of rules, and keeps what it works out; twelve drivers do. This one now
does too: android_input_keys_down() says which of the keys asked about
are down, and the driver's own walk of the binds is gone.

What it gave is what the frontend gives: a bind counts when it is
valid and its key is a key, and bound keys are kept from the RetroPad
while the keyboard is the core's, Game Focus excepted. One thing
comes with it: a stick's directions bound to keys are answered, which
this driver left at nothing.

Compiled with the NDK as the Android build does. Not run on a device.
2026-10-07 05:43:49 +02:00
LibretroAdmin 4534874f92 gl3: the half-float texture names are kept however many there are
The table that names the scRGB textures held thirty-two: the
thirty-third half-float texture loaded was drawn as SDR under the HDR
composite. The table grows with them, and goes with the context.
2026-10-07 05:43:06 +02:00
LibretroAdmin 84a84cea39 d3d11: a load whose converting staging twin cannot be made fails
An image the device stores in another format is converted through the
staging twin; when that could not be created the load handed out a
texture with nothing in it. It is released and the load returns 0.
2026-10-07 05:37:14 +02:00
LibretroAdmin 86b5cba2fd harness: every way a port's mapping changes is seen in the next frame
The frontend keeps what it works out from a port's mapping - which of
its binds a pad could be behind, the pad binds resolved between the
user's and the controller's profile, the controller's mask for the
poll. A kept thing that misses a change gives a core input that looks
right and is not, and nothing checked the changes end to end: the
"binds change count" lane checks that each way of changing a mapping
is counted, not that a read after it sees it.

A lane does: with a controller read through the per-poll copy, as the
desktop drivers' are, and the core reading button by button and then
as a mask, each change is made and the very next frame is read -
- the user binds a pad button, and takes the bind away;
- autoconfiguration gives the controller another profile;
- another controller is chosen for the port and the first chosen
  again (Device Index: the setting is written and nothing is told);
- a button is remapped, and the remap undone;
- the controller is unplugged, and plugged in again.
Both what should now press the button and what should no longer.

It passes as the tree is. With the check that a port's kept pad binds
are still current taken out, it fails at the first change.

The stand-in pad answers for one pad index, which a lane can now set;
0 as before for the others.
2026-10-07 05:35:27 +02:00
LibretroAdmin 641d4d9bb0 vulkan: an upload's place on the batch is taken before its copy is recorded
The batch kept what a copy reads from in a record made after the copy
was recorded; when that record could not be had, the staging memory
was destroyed with a command already naming it on the open batch. The
record is reserved first, and an upload with none is never recorded.

The batch also answers to what the API says: a command buffer that
cannot begin is not handed out, one that cannot end or be submitted is
released with its staging rather than left waiting on a fence that
never signals, and a refused submission notes a lost device as the
frame's does. A streamed update checks the same calls, and signals its
slot's fence with an empty submission when its own was refused, so the
slot does not stay busy for good.
2026-10-07 05:35:12 +02:00
LibretroAdmin 8ffe44bf39 gfx_surface: a dropped still update is sent again with its image; the decode generation does not come round
The wrapper lets go of a node's image descriptor once it has run it,
so the retry of a dropped still update went out without one and the
driver refused it. The retry gives the descriptor back first.

A decode's generation was eight bits wide: an old decode still out
while 256 newer requests came and went matched the current one again
and landed its stale image. It is 32 bits now.

The still sample's wrapper lets go of the descriptor as the real one
does, and covers both.
2026-10-07 05:18:40 +02:00
LibretroAdmin 4faa0041a1 win32: a video driver that falls back says so before RetroArch closes
When a video driver cannot start and another is to be used instead -
OpenGL too old for the driver chosen, Direct3D 11 not there -
video_driver_force_fallback() sets the other driver, saves the
configuration, shows a message that says so, and exits: RetroArch
starts with the other driver the next time.

On desktop Windows the message was never shown. It is behind the
usual "this is desktop Windows" conditions and one more, that
WINAPI_FAMILY not be defined - there to keep UWP out. <windows.h>
defines WINAPI_FAMILY in every build, to the desktop family where
nothing else was asked for, so the message was compiled out there
too: video_driver.o did not refer to ui_msg_window_win32. RetroArch
closed without a word and worked on the second start.

__WINRT__ is what says a build is not desktop Windows
(retro_environment.h), and is the condition ui_win32.c - where the
message window is - is compiled under in griffin.c and the Makefile.
The same wrong condition was on the release of a window nobody took,
corrected two commits ago; this was the place it was copied from.

Under Wine, with a fallback made to happen: a window titled "Error"
comes up and RetroArch waits for it, where there was none.
2026-10-07 05:06:20 +02:00
LibretroAdmin 2fdd56ee67 statistics: the Priority line names the audio driver's own thread
audio_driver_t gains thread_grant, which reports what a driver's own
device thread was scheduled under; the threaded wrapper forwards it.
The WASAPI pump and the WDM-KS refill thread record theirs - MMCSS,
high, no when refused, - when not asked - and the Priority line names
it as "device" where the driver has such a thread.
2026-10-07 05:05:16 +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
LibretroAdmin 9e32f3ec7d d3d11: a loaded texture takes its staging twin only when updated in place
Every sampled texture was created with a CPU-written staging twin that
lived as long as it did: two textures and the system memory of the
image for each menu icon. A loaded image goes straight in with
UpdateSubresource when the texture has its format; the twin is made on
the first in-place update, or for an image the device stores in another
format, where the copy converts it.
2026-10-07 04:56:00 +02:00
LibretroAdmin 6b30506b3e vulkan: texture uploads recorded between two frames go up in one submission
Each static texture load made a command buffer, a fence and a queue
submission of its own: a hundred icons at a menu's context reset were a
hundred submissions under the queue lock. The uploads, the compressed
loads, a streamed texture's first transition and the glyph atlas copies
now record into one batch, submitted once ahead of the frame - or of
any other submission of the driver's that may read them - with one
fence, and released together once it has signalled.

The threaded_video harness holds Vulkan to one submission for a batch of
loads; GFX_INSTR_UPLOAD_SUBMIT counts them.
2026-10-07 04:45:27 +02:00
LibretroAdmin d1a884c07b input: whether the pad copy is this poll's is tested where it is asked
input_snapshot_pad() hands out a controller's per-poll copy, taking it
if this is the poll's first look. It is called for every axis and hat
read through the copy - nine times a frame for a controller with two
sticks - and all but the first only want the pointer: a call, the
function's prologue and its way out, for one test of a bit. The test
is inline and the taking is its own function.

The same count as the commit before: 5061472 before, 5033490 now. With
no controller, and with the test controller on its own driver's loop
as the tree has it, the count does not move.
2026-10-07 04:33:14 +02:00
LibretroAdmin 9c1cb9d9aa input: "no button" is not asked of the pad
A stick at rest falls through, at every read, to the buttons that can
be bound to its directions; a button's pressure with no axis behind it
goes to its button. Where no button is bound - the usual case for a
stick - the pad driver was asked whether "no button" (NO_BTN) is
down: each way, each axis, and through the frontend's per-poll copy of
the pad, which has to sort a hat from a button to find that it is
neither. It is not asked.

Instructions in the frame's input with a core that makes an ordinary
core's reads, callgrind over 300 frames, the scripted test controller
read through the per-poll copy (as for the two commits before this,
in the build measured only): 5193748 before, 5061472 now.

The stick, single-axis and pressure functions before and after, taken
from the source against a stand-in pad that answers "no button" the
way the drivers do, over two million random binds, pad states, keys,
deadzones and modes: the same values, and 3439848 asks made of the pad
where there were 6191017.
2026-10-07 04:33:11 +02:00
LibretroAdmin 103d95ee39 win32: a replaced window's late focus loss does not take the keyboard away
Turning threaded video on while a game runs left the keyboard dead
until the window lost and regained the focus (Alt-Tab away and back).

The toggle moves the window to another thread. The new window comes
up on the new thread and takes the focus; the old window is told it
lost the focus through its own thread's queue, and that thread gets to
the message after the new window is up. The input drivers keep one
focus for the process (winraw_focus, and DirectInput's own), set and
cleared by the window procedure - so the old window's loss, arriving
last, cleared it, with a focused window on screen.

A guard for this existed for a window losing the focus while it is
being destroyed (win32_retiring_hwnd). This loss comes before that.

A focus loss from a window that is not the main window is not passed
to the input driver, and does not release the mouse clip: it is not
the process losing the focus. If the focus does leave, the main window
is told so itself.

Under Wine, a build with a way to toggle threaded video at a given
frame as the menu's setting does, keys pressed before and after:
- raw input, off to on: before, the old window's loss reached the
  driver after the new window's gain and no key arrived afterwards;
  now the loss is dropped and the keys arrive.
- DirectInput, off to on: the same.
- raw input, on to off: the keys arrive.

samples/gfx/win32_window_proc gains the case - a window with the same
procedure that is not the main window loses the focus, then the main
window does - and fails without this ("its loss reached the input
driver: yes").
2026-10-07 04:33:08 +02:00
LibretroAdmin dc6f6c61d0 win32: a window nobody took is taken down on the desktop too
A video context being freed may leave its window up for the next
driver. If that driver runs on another thread, is of another kind, or
does not come, the frontend takes the window down once the next driver
is up (win32_window_release_kept()).

It never did. The three places that do it asked, with the usual "this
is desktop Windows" conditions, that WINAPI_FAMILY not be defined -
meant to keep UWP out. <windows.h> defines WINAPI_FAMILY in every
build, to the desktop family where nothing else was asked for, so on
the desktop the call was compiled to nothing: video_driver.o did not
refer to the function at all.

The one case that depends on it is threaded video being turned on or
off while running, which moves the window to another thread: the new
driver cannot take a window that belongs to another thread, and
nothing took it down. The old window stayed up behind the new one,
still the old thread's focus window - which is what set off the
keyboard loss fixed in the next commit.

A UWP build is one where __WINRT__ is defined (retro_environment.h,
included here ahead of the test); that is the condition
win32_common.c itself is compiled under in griffin.c, and the one the
rest of video_driver.c uses.

Under Wine, threaded video toggled while running, both ways: before,
no release and two windows left; now "The window left up was not
taken: destroyed" and one window.
2026-10-07 04:33:04 +02:00
LibretroAdmin 523a884f0c statistics: one line for what each thread was granted
The LATENCY block gains a Priority line naming what the main, video
and audio threads were granted - MMCSS, high, no when refused, ask
while a broker answers, - when not asked - and whether the low-latency
power plan is active, with or without idle states held off. Each
thread records its outcome once as it starts and as it ends; the line
is left out when nothing was raised or applied.
2026-10-07 04:24:49 +02:00
LibretroAdmin a7c6bc5a71 d3d12: a texture unloaded is released behind the fence, not after a drain
Unloading a texture drained the queue each time: a GPU round trip for
every icon a context destroy or a playlist refresh lets go. The texture
is retired behind the next signal instead and released once the fence
has passed it, at the frame's own drain. The compressed load's one-shot
copy list and upload buffer go the same way, behind a signal of their
own, rather than being waited on.
2026-10-07 03:59:25 +02:00
LibretroAdmin 2f2bac83b7 gfx_display: the registered texture loads go
Every icon slot is a gfx_surface still that owns its texture now, so
the registry of loads into a caller's handle, the cancels over a
slot's range and the file and buffer loads that fed it have no users.
The 3DS bottom textures decode and upload through the driver's own
load, the iOS app icons are stills whose texture is read at each call,
and the menu drivers take their stills from gfx_surface_still.

The texture_load sample becomes gfx_surface_still: the still lanes,
without gfx_display linked.
2026-10-07 03:41:57 +02:00
LibretroAdmin ef4bb8bddc input: a controller's plain buttons are read from the copy in hand
Working out a port's RetroPad mask from the per-poll copy of its
controller asked for each bound button through
input_snapshot_button(), which finds the copy again, checks that it
is this poll's and sorts hats from buttons, for every one: some forty
instructions a button for what is one bit of a copy the caller could
hold. The copy is taken once and a plain button is a bit of it; a
hat's direction, or a button past what the copy holds, is still asked
for the long way.

The same count as the commit before, the test controller read through
the copy: 5425679 before, 5193748 now. Both commits together: 15.3%
fewer than the 6133564 it was.
2026-10-07 03:31:12 +02:00
LibretroAdmin d2efc20a13 input: a controller's RetroPad mask is worked out once a poll
A port's RetroPad mask is asked for by several readers each frame -
the hotkeys, the frame's view of the port for the core, the poll's
own - and for a controller read through the frontend's per-poll copy
of it (xinput, dinput, raw input, udev, linuxraw, SDL2, SDL3) it was
worked out again from the binds for each of them: three times a frame
for a port with a controller, from a copy that does not change
between them.

It is kept for the poll (pad_state_cache), with the lifetime of the
copy it is worked out from: dropped at the poll, when the copy is
dropped, and when the port's binds change. It is given back only for
a read of the port's own binds by its own controller, with the same
axis threshold and the same say on full-range triggers; any other
read is worked out as before.

Instructions in the frame's input with a core that makes an ordinary
core's reads, callgrind over 300 frames, the scripted test controller
plugged in. Its driver is not one of those read through the copy, so
for this count it was made one (it was given get_buttons() and put on
the list, in the build measured only): 6133564 before, 5425679 now,
11.5% fewer. As the tree is, where that driver has its own loop, and
with no controller, the count does not move (4792266 and 4486637).

A build that worked out every mask afresh the way it was and stopped
on a difference ran the frontend harness, whose lanes use controllers,
remaps, full-range triggers and changes of profile: 1114020 masks,
174941 of them kept from earlier in the poll, each the same.
2026-10-07 03:31:08 +02:00
LibretroAdmin d5f7cca4da win32: the low-latency power plan keeps links and ports awake
The plan copy also sets the processor energy preference to performance
and turns off PCI Express link state power management and USB
selective suspend, so the GPU, storage and USB devices do not wake
from low-power states mid-frame. A system that lacks one of those
settings gets the plan without it.

Power Management gains Disable Processor Idle States, off by default:
with the plan on, the copy also keeps every core out of its idle
states. Changing it while the plan is active replaces the copy; if
Windows refuses it, the plan applies without it and the setting turns
itself off.
2026-10-07 03:28:26 +02:00
LibretroAdmin 9c3cd31145 gfx_widgets: the icon slots are stills
The widgets' icon set, the volume widget's icons, the screenshot and
the load-content icon and the AI service overlay are gfx_surface
stills, freed by the widget that holds them; a decode still out when
one goes lands nowhere. The icon set reloads into the stills it has at
a context reset, so the textures up stay until the new ones land.

gfx_surface_still gives the still in a slot, made on first use;
gfx_surface_submit_buffer decodes an image held in memory.
2026-10-07 03:27:33 +02:00
LibretroAdmin f3e1de4a6a win32: the low-latency power plan pins maximum processor state too
The copy sets maximum processor state to 100% alongside the minimum,
so a plan that caps the processor below full speed no longer leaves
the copy throttling.

samples/frontend/win32_power_plan runs the power plan code from its
shipping text against a fake powrprof: a capped plan, recovery after a
run that did not restore, a plan switched by the user, a refused write
and no powrprof at all.
2026-10-07 03:17:22 +02:00
LibretroAdmin 9808a3bd97 menu: the explore and contentless core icons are stills
Each system icon the explore view holds, and the contentless cores
list's fallback and per-core icons, are gfx_surface stills: the owner
frees them, and a decode still out when it does lands nowhere. The
per-core icon lives in the map itself, with no handle allocated beside
it.

menu_icon_ownership gains a contentless lane: a core whose info says it
runs without content, its icon and the fallback, counted at deinit.
2026-10-07 03:14:58 +02:00
LibretroAdmin 5b21576f59 materialui: the icon slots are stills
The texture list, the wallpaper and each playlist icon are gfx_surface
stills: the list goes at context destroy, a playlist icon with its
entry, and a decode still out for either lands nowhere once it is
answered.
2026-10-07 03:07:01 +02:00
LibretroAdmin 5521ee9d9a xmb: the icon slots are stills
The theme list, the wallpaper and each playlist node's icons are
gfx_surface stills, so every texture has one owner: a node frees its
own when it goes, the tabs point at the list's stills instead of
copying their handles every frame, and the context destroy frees the
list. gfx_surface_take_image moves the pixels of an image the caller
keeps into a still, for the wallpaper that arrives through the menu's
load_image.
2026-10-07 02:51:08 +02:00
LibretroAdmin b3d6736d0f input: a port's sixteen pad binds are resolved when they change, not at every read
Each poll, for each port with a controller, the RetroPad's mask is
worked out from the binds: sixteen of them, and for each the choice is
made again between the port's own bind and the controller's
autoconfig profile - for the button, and for the axis.

The choice is made when a bind changes. A port's pads
(input_port_pads_t) keep the button and the axis behind each of the
sixteen, and input_snapshot_state() reads those, and only for the
binds a pad is behind. It does so where the read is of a port's own
binds by its own controller; any other read goes through all sixteen
choosing as it goes, as before.

One call for an ordinary controller's profile - buttons, the D-pad,
two triggers on axes - with the loop as it was and as it is, both
taken from the source against a stand-in pad: 484 instructions before,
341 now.

That is not in the whole-frame count, where it reads 4484289 before
and 4486637 now without a controller and 4778205 and 4792266 with the
scripted test controller: that controller's driver has its own loop
and is not read through this one, so what the count shows is the
sixteen more values made when the binds change at startup.

A build that worked out every mask both ways and stopped on a
difference ran the frontend harness, whose lanes use controllers,
remaps, triggers and changes of profile: 249997 masks, all from the
resolved binds, each the same.
2026-10-07 02:49:09 +02:00
LibretroAdmin 8f50d93e5a threaded_video: the context api lane follows a driver's own context driver
A driver with a context driver of its own is known by that, behind the
wrapper as well; only one without is known by its ident. The lane swaps
the ident, and the GPU names keyed on it, only on that path, so it passes
on gl, glcore and vulkan as it does on the null driver.
2026-10-07 02:23:08 +02:00
LibretroAdmin 91267bb721 input: the copy a pad driver is read through is found with one compare
input_snapshot_for() says whether a pad driver's reads go through the
frontend's per-poll copy of it. It is asked several times a frame, and
each time went through the checks that decide it - a call, three
function pointers - before finding that the copy already stands in for
that driver. A driver the copy stands in for was decided when the copy
was set up; that is looked at first. The test switch that forces the
copy on clears it, so that it is decided again.

Instructions in the frame's input, as in the commit before: with no
controller (the udev driver, which is read through the copy) 4518909
before and 4484289 now. With the scripted test controller, whose
driver is not read through the copy and so never takes the short way,
4749335 before and 4778205 now: the one compare it adds, and the rest
where the compiler put things.
2026-10-07 02:22:33 +02:00
LibretroAdmin 3e557bd593 input: whether a pad is behind a bind is asked of the port's own controller
A read of a bind no pad is behind is answered from the keys and the
mouse without going to the pad drivers, and the hotkeys nothing could
be holding are not gone through. "No pad is behind it" was kept over
every controller's autoconfig profile at once, which is right
whichever controller a read goes by - and means that one controller
plugged in puts a pad behind the binds of every port. Every port's
mask went back to both pad drivers each frame, and each port's sixteen
binds were walked again each poll, for a controller that is on one of
them.

It is asked in two steps now. Any controller's profile first, as
before: one test, and with no controller plugged in it says no for
every bind and nothing else is asked. Only of a bind that has one is
the port's own controller's profile asked - the one its Device Index
names (input_port_pads_t) - and only for a read that goes by that
controller, which the caller says with the profile it passes. A read
by another controller is answered by the first step, as before. If a
port's Device Index has changed without the binds being counted as
changed, the read that finds the mismatch makes the port's pads again.

With a core that makes an ordinary core's reads under X11,
instructions in the frame's input counted by callgrind over 300
frames: with the scripted test controller plugged in and
autoconfigured, 5107631 before and 4749335 now, 7% fewer; with no
controller, 4478369 before and 4518909 now - the same work, and some
40 a frame of the difference is made once at startup.

A build that answered every such read both ways and stopped on a
difference ran the X11 session with the test controller pressing
buttons - 316151 masks answered without the pad drivers, nearly all
on ports where some controller has a pad behind the binds - and
without a controller: none differ.
2026-10-07 02:22:29 +02:00
LibretroAdmin 78a3c6dcb4 gfx_surface: stills own a menu icon's texture; ozone's slots are surfaces
A still is a surface with no image until given one: gfx_surface_submit_image
takes a decoded image and uploads it through the threaded wrapper when it is
up, keeping the texture up until the new one has landed and queueing an image
given during the flight behind it. submit_file decodes on the spot;
submit_path decodes on the task queue, where only the newest path lands and a
surface freed meanwhile goes once the decode answers.

Ozone's theme, tab, icon and node slots hold such stills, so each texture has
one owner and the frame reads handles through GFX_SURFACE_HANDLE. The
registered loads in gfx_display go once the other owners are converted.

The texture_load sample covers the still: in-place update of a same-size
image, replacement on another size, an image given in flight, a free in
flight, stale and superseded decodes, and a free while decoding.
2026-10-07 02:10:32 +02:00
sonninnos ca82e0c697 Log format adjustments 2026-10-06 23:53:20 +03:00
sonninnos 2b5e9407c7 Menu item relocations 2026-10-06 23:50:22 +03:00
sonninnos 7319bc37de Video stats alignments and cleanups 2026-10-06 23:20:56 +03:00
LibretroAdmin 51ed7a21ac input: the hotkeys that could be held are walked, not looked for
The delivery loop went through all sixty-odd hotkeys to test each
one's bit in the mask of what could be holding it, and then did
nothing for nearly all of them: the test itself, sixty times, was most
of what was left of the loop. It walks the set bits of the mask
instead - a word at a time, lowest first, so in the order it always
went.

With a core running under X11 and no controller, instructions in the
frame's input - the poll, the hotkeys and the core's reads - counted
by callgrind over 300 frames, this and the three commits before it:
7375417 before, 6609824 now, 10% fewer. With a core that makes only an
ordinary core's reads (sixteen buttons, the mask, the sticks) it is
4697630, some 15,700 a frame.
2026-10-06 19:54:47 +02:00
LibretroAdmin b2b0d190d3 input: "no key" is not asked of the driver
The hotkey evaluation has a rule for a menu toggle and a hotkey
enabler that share a key. They share one where neither has one, too -
every port but the first, as the binds come - and the driver was then
asked, each frame for each such port, whether key 0 is down. The
drivers answer no to that themselves (id && ...); it is not asked. The
rest of the rule runs as it did.
2026-10-06 19:54:44 +02:00
LibretroAdmin 67d10b1f9e input: a core's read asks for the settings only where it uses them
input_driver_state_wrapper() fetched the settings at the top of every
read. A button served from the frame's view of the port - most reads -
uses none. They are fetched on the paths that do: compiling the view,
and a read that is not a RetroPad button.
2026-10-06 19:54:40 +02:00
LibretroAdmin 006472803b input: a port's sixteen buttons go through the core's rules in one go
A read of the whole pad as a mask - which is how a port's buttons are
compiled for the frame, once a frame for every port a core reads -
called input_state_device() sixteen times, once a button. That is the
rules a button goes through on its way to the core: the remap, what
the mapper and the overlay hold, hold and turbo.

Hold and turbo keep state for each button and are why it is a button
at a time. Where the port has neither going - nothing held, no turbo
armed or running, which is nearly always - they do nothing to any
button, and what is left is the same three masks for all sixteen
(input_state_device_mask_plain()). Where one is going, or the Remote
RetroPad is in use, the buttons are gone through as before.

Checked against the sixteen calls, both taken from the source, over
two million random states - remaps, binds that are not valid, mapper
and overlay buttons, the menu up or not, hold and turbo at rest and
not: 929276 taken in one go, each the same mask and the same hold and
turbo state after.
2026-10-06 19:54:36 +02:00
LibretroAdmin 80d6da1a91 input: include the header compat_ctz() is in
input_driver.c does not build: "implicit declaration of function
'compat_ctz'" in input_port_keys_refresh().

The commit before this one, which walks the set bits of the keys that
are down, uses compat_ctz(). The #include of <compat/intrinsics.h> it
needs was in an earlier patch of the same series (the one that walks
the hotkeys), which is not on master: that patch and the three before
it were never applied, and this one was handed over as going on top of
them. It should have carried its own include.

This is that include, so that master builds as it stands.
2026-10-06 19:54:32 +02:00
LibretroAdmin 7f1e8faee2 d3d12: the compressed texture load runs on the video thread under threaded video
Its one-shot copy goes through the driver's queue and its SRV comes out
of the descriptor bitmap, so with threaded=true it takes the same round
trip onto the video thread as the plain load, unload and update do.
samples/gfx/d3d_texture_marshal reads the D3D10/11/12 objects'
relocations: every poke entry a caller reaches with threaded=true that
touches the device's own state names video_thread_texture_handle.
2026-10-06 19:52:53 +02:00
LibretroAdmin a0d9d56820 input: the keys that are down are walked, not looked for
Two loops left that went a bit at a time through bits that are nearly
all clear:

- each poll, which of the keys a port's binds name are down: the
  driver's answer has a bit a key, some sixty of them, and every one
  was tested to find the few that are set;
- the RetroPad buttons bound to a mouse button, when the pad's mask is
  asked for.

Both walk the set bits. What the first makes is checked against the
loop as it was over three million random answers and bind lists: none
differ.

With a core running under X11 and no controller, instructions in the
frame's input counted by callgrind over 300 frames: 6609824 before,
6390563 now; with a core that makes only an ordinary core's reads,
4697630 before, 4478369 now, 4.7% fewer.
2026-10-06 19:48:17 +02:00
LibretroAdmin 4f38a04d29 video_thread: async completions are delivered only from the main thread's push
The uploads the video thread finished are handed to their owners from
video_thread_frame() after, not before, the video thread's own way
through it: a cached frame presented from that thread, as the Win32
size/move loop and a driver's read_viewport() do, re-enters the push
there, and the owners of those completions - gfx_display's registered
loads, the menu drivers' icon slots - are main-thread code. The
threaded_video harness has the lane: alive() presenting from the video
thread every few frames with a load posted every frame, every done()
on the pushing thread.
2026-10-06 19:42:08 +02:00
LibretroAdmin f95d399748 gfx_display: one registered load from the decode to the slot
A menu icon load is registered in gfx_display from the moment the file
is handed to the task queue until its texture is in the slot: the
decode's completion carries the same entry on into the upload, so an
icon load and a texture load into a slot are ended the same way, by
cancelling the slot's range before it is reset or freed. The
per-module generation counters that guarded the decode's callback go,
and with them the task-side icon loader; the owners that lacked a
range cancel on free (XMB's nodes, Ozone's theme textures, MaterialUI's
playlist icon array, the explore and contentless-cores icon arrays,
the volume widget) have one.
2026-10-06 19:29:28 +02:00
LibretroAdmin 79d8c504ce gfx_display: a quad with no texture is a solid colour the display driver draws
The display drivers take a draw with texture 0 as a solid-colour quad:
D3D10/11/12, GL2, GL3, Metal, GX2, GXM, 3DS and RSX sample a 1x1 white
texture of their own, made at init and gone with the driver; GL1 draws
the vertex colours with texturing off; D3D8, D3D9, Vulkan, SDL and GDI
already had their own answer. The frontend's shared white texture goes
with that, along with its load on every menu context reset and the
init/deinit pairs the menu drivers kept in step, so no menu draw depends
on a texture that lands a frame later.
2026-10-06 19:05:20 +02:00
libretroadmin fda5e257b8 video_thread_hw: an OpenGL core that keeps its framebuffer stays on its slot
The ring hands the core a different framebuffer each frame, as
libretro.h allows, but a core that asks once at context_reset and keeps
it (mupen64plus-next through glsm) renders into one slot while every
slot is presented, so two frames in three were never drawn (#19727).
The ring now remembers the slot the core last asked for; a frame the
core did not ask during is in that slot, which is presented and stays
the core's, with the wait that already precedes the next frame. A core
that asks each frame is unaffected.

egl_hw_core gains a mode that caches the framebuffer, run for each slot
of the ring.
2026-10-06 19:00:13 +02:00
LibretroAdmin aef2b5a715 wayland smoke test: wait for each window, and find the compositor's as the one it brought
The Wayland input job failed again, further on: "no windows to press
keys in". The compositor had started; the script then slept a second
and took its window to be up, started a second window (xclock), slept
another second and took that one to be up too. One of the two was not
there yet, and the message did not say which. As with the failure
before it, nothing of RetroArch had been started.

- The compositor's window is waited for, for up to a minute. It is the
  visible window that was not there before the compositor was started:
  the script asked for "the last visible window", and before the
  compositor's is up that is a window of the X server's own, so a wait
  for it would have ended at once on the wrong one. (Its title cannot
  be searched for: it is set where xdotool's search by name does not
  look.)
- The second window is waited for the same way.
- If one does not come the test says which, lists the visible windows
  and shows the compositor's log.
- A line says how long after the X server was started everything was
  up, so that a slow runner shows in the log of a run that passes.

Run here on a Wayland build: it passes three times running, passes
with a second window made to appear six seconds late, and with one
that never appears fails after the minute naming it.
2026-10-06 18:59:50 +02:00
LibretroAdmin a2da8fb966 menu: a node's icon slot holds only a texture the node owns
Ozone's sidebar draw stored the shared cursor texture into a playlist
node whose own icon had not landed, and the context destroy then
unloaded that one handle from the node and from the icon set; under
threaded video, where icons land a frame or more after they are asked
for, that is a heap-corruption crash on startup. The sidebar and XMB's
horizontal list now draw the cursor for a node without an icon and
write nothing back, and the explore view's node keeps no copy of it.
MaterialUI's playlist icon array invalidates the decodes in flight for
it when it is freed, so a rebuild under a pending load lands nowhere,
and its context destroy unloads the white texture while the driver
that made it is still there.

samples/menu/menu_icon_ownership accounts for every handle the three
menu drivers hold against a counting null driver, one run per driver,
in the menu harness workflow.
2026-10-06 18:42:08 +02:00
LibretroAdmin f6e88289a3 win32: the context API is the wrapped driver's, and a window going down keeps its focus loss from the input driver
video_context_driver_get_api() names a driver without a context of
its own by its ident, through video_driver_get_ident(), which sees
past the threaded wrapper; the GPU names the Direct3D drivers publish
are keyed on that API, so the GPU Index shows them threaded as well.

A window left up for a driver that did not take it is destroyed once
the next window is up, and loses the focus as it goes; the input
driver keeps one focus for the process, so that late loss left the
keyboard dead until the next Alt-Tab. Messages from a window on its
way out no longer reach the input driver.

samples/gfx/threaded_video pins the API behind the wrapper, and
samples/gfx/win32_window_proc the focus loss of a window going down.
2026-10-06 18:41:53 +02:00
sonninnos cdcbb40902 Remove dupe config bool 2026-10-06 19:32:37 +03:00
sonninnos 6ef24292b2 Menu icon cleanups 2026-10-06 19:20:01 +03:00
LibretroAdmin da72cbb03d winraw: whether the cursor places every mouse is asked once a poll, not once a report
For each mouse report that moves, winraw_update_mouse_state() asked
whether the menu was up and whether an overlay was configured - two
calls a report, and a mouse sends a thousand reports a second, or
eight thousand. The answer is taken at the start of each poll and
used for the reports read in it.
2026-10-06 18:11:02 +02:00
LibretroAdmin 72ff28cb5b input: a port with no pad or key behind it costs next to nothing a frame
Every port is asked for its RetroPad mask every frame, and its keys
are refreshed every poll, whether anything is bound for it or not.
With one keyboard and one controller - or none - that is seven ports
of nothing.

- The mask of a port none of whose sixteen a pad may be behind is what
  the keys and the mouse hold: it is answered from those bits, and
  kept for the frame as before, without the call into
  input_state_wrap_slow() and both pad drivers.
- The key refresh of a port none of whose binds names a key returns
  at once.

A build that worked out each such mask both ways and stopped on a
difference ran the X11 keyboard session: 421282 masks, each the same.

With a core running under X11 and no controller, instructions in the
frame's input - the poll, the hotkeys and the core's reads - counted
by callgrind over 300 frames: 8624209 before this and the commit
before it, 7799179 after that one, 7375417 now: 14% fewer, and 37%
fewer than the 11698705 it was before the frame's input was profiled.
2026-10-06 17:56:37 +02:00
LibretroAdmin 1947552a12 input: the hotkeys gone through are the ones something could be holding
Each frame the hotkeys are delivered by a loop over all of them, some
sixty: for each, every other source is asked (a function call that
goes through the command interfaces and the overlay), the bind is
read, and only then - nearly always - nothing is done, because it is
not held.

What could be holding a bind is in hand as bits: the keys that are
down, the binds a mouse button or a pad may be behind, what the
overlay holds. The loop skips a hotkey that is in none of them. The
menu toggle is always gone through - it acts on being let go - and so
is every hotkey where the keys are not the frontend's to know (a
driver that does not give it its keys) or a command interface is
open, whose presses are not kept as bits. The order is as it was.

A build that still worked out each skipped hotkey as before, and
stopped if one was held, ran the X11 keyboard session, both overlay
tests and the X11 smoke test: 2.7 million hotkeys looked at, 2.6
million skipped, none of them held.
2026-10-06 17:56:33 +02:00
LibretroAdmin 722d37ff47 runloop: a core that has run no frame is not held back for focus
With Pause When Not Focused on, a window without focus pauses the
core, and the pause keeps presenting the last frame. A core that has
not run a frame yet cannot be paused, so the pause was refused and the
loop slept instead, presenting nothing, until the window had focus.

A window that is given focus only once it presents never gets it that
way. An OpenXR session is one: on the Quest, a core started from the
menu after launching RetroArch left the new session waiting for its
first frame, with a blank screen. The same went for starting with
content from the command line.

Until b29c8f6fcf the two frames of held-back input after the menu
closed skipped the focus check for one iteration, which ran the core
once and made the pause possible. That hold is a wait for release now
and skips nothing.

The focus is looked at once the core has run a frame: it runs one,
then pauses and presents as any unfocused window does, and goes on
when the focus comes.

The content_load harness gets a lane with a driver that has focus
only after three frames presented, and a core started from the dummy
session. Without the change nothing is presented and the core never
runs.
2026-10-06 17:52:14 +02:00
LibretroAdmin d2d2fb21b9 runloop: fast-forward ratio stays out of the menu, high ratios reach their rate, vsync and Scanline Sync apply live
Scanline Sync holds the focused menu as vsync does, so the frame limit
the menu leaves standing - the fast-forward period - no longer runs a
timer there; between locks the gap limiter bridges at the display rate.
Closing the menu over running content restores the fast-forward limit.

The sleep margin may follow the OS timer up to 2 ms, and at most a
whole period, so a fast-forward period shorter than the timer can
sleep is spun to its deadline and every ratio reaches its rate.

Video, Scanline Sync and Adaptive VSync toggles reapply the driver's
blocking state, and every caller derives it from runloop_vsync_blocks():
vsync blocks only without Scanline Sync, a forced nonblock or
fast-forward. Switching Scanline Sync on reads the output lines afresh.

The menu stick-to-D-pad mapping computes its bit unsigned.

samples/runloop/pacing pins the menu row, fast-forward at 15x/30x/60x
over a 0.25-2 ms timer, and the blocking predicate.
2026-10-06 17:29:00 +02:00
LibretroAdmin 039887be6f wayland smoke test: wait for the X server and the compositor, and say why one did not start
The Wayland input job failed with "the compositor did not start":
Weston's log stopped after it loaded its X11 backend, and the fifteen
seconds the script gave it ran out. Nothing of RetroArch had been
started - the scenarios come after - so no change to RetroArch was
being tested when it failed. Why Weston stopped there is not known:
the script showed five lines of its log and nothing of the X server's.

The script started the X server, slept a second and took it to be up;
then gave the compositor fifteen seconds. Now:
- the X server is up when something can connect to it and be answered
  (xdotool getdisplaygeometry), for up to a minute, and the test stops
  with its log if it has exited;
- the compositor has a minute for its socket to appear, is seen to
  have exited instead of being waited for, and is started a second
  time if the first does not get there;
- if it still does not start, the test says how it ended - still
  starting, or its exit status - and shows both logs whole.

The X11, Windows (Wine) and Vulkan resize smoke scripts started their
X server the same way and get the same wait.

Run here on a Wayland build of this tree: the test passes three times
running; with an X server made to take five seconds and a compositor
made to fail its first start it passes; with a compositor that never
starts it fails in eight seconds with the status and the logs.
2026-10-06 17:28:16 +02:00
libretroadmin eb724f8635 Remove unused functions 2026-10-06 17:08:23 +02:00
LibretroAdmin 1f1306c66b input: the pad's RetroPad mask is not worked out where no pad is bound
Once a poll, for each port, the RetroPad's mask is worked out from the
frontend's copy of the pad: sixteen binds, each resolved between the
port's own and the pad's autoconfig, each looked up. With no
controller, or a port none is bound for, that is sixteen lookups of
nothing, ten times a frame.

Which of a port's sixteen a pad may be behind is kept, made again when
the binds change, and the mask is not worked out where it is none.

It goes by the count of changes to the binds, so a bind written
without being counted would not be read: every writer in the tree is
counted - binds read from a file now count one themselves - and the
frontend harness, whose lanes write the arrays directly, looks at them
before it runs frames and counts a change on a lane's behalf.

With a core running under X11 and no controller, instructions in the
frame's input - the poll, the hotkeys and the core's reads - counted
by callgrind over 300 frames: 11698705 before this and the commit
before it, 9548879 after that one, 8624209 now: 26% fewer in all.
2026-10-06 17:06:22 +02:00
LibretroAdmin 7bd967b7b1 input: a bind no pad is bound to is read with a test of a bit
Each frame the hotkeys are read one at a time through
input_state_wrap(): a dozen arguments, both pad drivers' binds walked
for the bind's button and axis, then the keys. Most hotkeys have no
pad behind them at all - a key, or nothing - and each still paid for
the whole call, sixty-odd times a frame.

The keys a port's binds name are compiled when the binds change, and
which of them are down is in hand each poll. Compiled with them now is
which binds a pad may be behind: the bind's own button or axis, or one
that any pad's autoconfig profile gives it (which pad a read goes by
is the caller's to say, so it is over all of them). A read of a bind
past the RetroPad's sixteen that no pad may be behind is answered from
the key and mouse-button bits, inline where it is asked; the work as
it was is input_state_wrap_slow(). Where the driver gives the frontend
its keys; another driver is asked as before.

A build with both answers worked out for every such read, and an abort
if they differed, ran the X11 keyboard session - 3.5 million reads,
2.4 million of them this way - and the frontend harness: none differ.
2026-10-06 17:06:19 +02:00
LibretroAdmin ab570216b0 overlay: a stick past the threshold is an integer compare, value for value the same
The overlay divided each of a stick's two values by 0x7fff as a float
to compare them with the axis threshold. It compares them as integers
with the two values the threshold comes to, found by bisection with
that very comparison when the threshold changes - what the menu does
since its own patch. The menu's helper becomes input_stick_limits(),
which serves both: the menu's comparison leaves out a value that meets
the threshold, the overlay's counts it.

Checked: the limits against the float comparisons, both kinds, for
every value a stick reads with each of those values as the threshold
and the floats either side of it - 100316 thresholds, 26.3 thousand
million comparisons, none differ; and the overlay's block against its
switch as it first was, over 6.4 million cases, none differ.
2026-10-06 16:43:48 +02:00
LibretroAdmin 54400d283a input: a stick is read whole, once a frame
A core asks for a stick an axis at a time, and each axis asked for was
its own reads of the pad. With a deadzone that is six: the stick's four
pad axes for its magnitude, then the axis's two again for its value.
Two sticks came to twenty-four reads of the pad a frame for a port,
and as many again each time the core was run again in the frame.

- input_joypad_analog_stick() read the stick's four pad axes for the
  magnitude and then each of them again through input_joypad_axis().
  The scaling is split from the read (input_joypad_axis_scaled()) and
  each of the four is read once.
- The core's reads go through input_port_stick_axis(): the first axis
  asked for in a frame reads the whole stick, and the rest of the
  frame's reads of it are given what was read. It is kept with the
  frame's other caches and dropped at the poll; a read made under
  another stick-drives-the-D-pad mode is made again.
- The menu reads each stick whole where it read its two axes apart.

Two sticks with a deadzone, in calls to the pad's axis function: 24
axis by axis, 8 with each stick read whole, and none for the rest of
the frame's reads (counted with both functions taken from the source,
against a stand-in pad). By the time a core reads, that pad is the
frontend's own copy of it from the poll, so these are not reads of a
device: what is saved is the calls, and with a deadzone a square root
and its arithmetic for every axis read but the first of each stick.
The frontend harness lane "sticks read once" holds that a stick's two
axes come from one read, that later reads in the frame are given it,
and that the poll drops it.

The whole-stick function and the axis-by-axis one, both taken from the
source, give the same values over two million random binds, pad
states, keys, deadzones, sensitivities and modes.
2026-10-06 16:43:45 +02:00
LibretroAdmin 866848e80b rcheevos: keep the inf/NaN bit patterns to binary32 float targets
rc_build_float writes the special values as IEEE-754 bit patterns only
when float.h describes binary32 storage (radix 2, 24-bit mantissa,
FLT_MAX_EXP 128), where they hold under -ffast-math. A float format
without those encodings gets the largest value instead.
2026-10-06 16:38:17 +02:00
LibretroAdmin 3bdcfee19a input: the RetroPad's sixteen buttons are gathered as one word
Each frame, for each port, the frontend went through the RetroPad's
sixteen buttons one at a time: was the bit set in what the controller
and the keys hold, and if not, did any other source hold it - which
called a function that went through the command interfaces, the remote
pad and the overlay for that one button. Sixteen times.

What the other sources hold is gathered as one word, once
(input_pad_buttons_other_sources()): the overlay's and the remote
pad's are words already, and a command interface's is read only when
there is one. The sixteen are then delivered with one OR, less the
buttons a wait for release is holding back. No loop is left.

With a core running under X11, instructions executed in the frame's
hotkey and button evaluation, counted by callgrind: 5437133 before,
4861050 now, 10.6% fewer.

Checked against the loop as it was, both taken from the source, over
three million random states of every source, the wait and what was
already held: none differ. It leans on the line before it, which marks
what the controller holds as not the overlay's alone, as the loop did.
2026-10-06 16:14:21 +02:00
libretroadmin efc2f47027 gfx_display: a texture load into an occupied slot replaces it once loaded
Under threaded video a load lands a frame or more after it is asked
for. A slot that was unloaded first drew nothing meanwhile: XMB's
dynamic wallpaper path did that on every horizontal move, taking the
white texture with it, so the whole screen went dark (#19725). A load
now keeps the slot's texture until the new one has landed, then unloads
it, on every path; gfx_display_init_white_texture() loads once and is
otherwise a no-op, and only a context's end unloads it. XMB and
MaterialUI load their wallpaper over the old one, and a dynamic
wallpaper path with no file behind it counts as no wallpaper.

samples/gfx/gfx_display_texture_load covers the replacement, the queued
pair, the cancel and the white texture, under ASan and UBSan, and
builds with mingw-w64.
2026-10-06 15:48:37 +02:00
LibretroAdmin 8acda215a3 menu: a stick past the threshold costs two integer compares, value for value the same
For a stick to move the menu, its value was divided by 0x7fff as a
float and compared with the axis threshold, in four blocks written
out: below minus the threshold on y, above it on y, the same on x,
each setting one of two bits.

A stick reads one of 65536 values and the division only grows with the
value, so each comparison holds from some value on. Those two values
are found by bisection with that very comparison, when the threshold
changes (input_menu_stick_limits()), and a stick is compared with them
as an integer: what the menu takes is the same value for value. The
bit is worked out from the axis and the way - the D-pad's four and the
right stick's four binds are each numbered in a row, held at compile
time.

Checked against the four blocks as they were for every value a stick
reads, both axes, in a playlist and not, with each of those values as
the threshold and the floats either side of it, the settings' own
steps, and thresholds that are negative, past the ends, infinite or
not a number: 100316 thresholds, 26.3 thousand million cases, none
differ.
2026-10-06 15:44:37 +02:00
LibretroAdmin 03781b2560 overlay: its stick-drives-the-D-pad switch reads the table of the modes
The overlay had its own switch over the stick-drives-the-D-pad modes,
with the same four lines - past the threshold one way or the other on
x, and on y, set a button - written out four times. It reads which
sticks a mode takes from the table the pad code reads, and the four
lines are one macro (INPUT_OVERLAY_STICK_TO_BUTTONS), the comparison
left as it was. The twin-stick mode is still its own case: its right
stick is the face buttons.

Checked against the switch as it was, both taken from the source: every
mode and a grid of stick values and thresholds, and random ones - 6.4
million cases, none differ.
2026-10-06 15:44:33 +02:00
LibretroAdmin 978b9a4cb2 CI: no input driver's table may outgrow struct input_driver
The UWP build failed with C2078, too many initializers, at the end of
input_uwp's table: the commit that gave the UWP, udev, web and Cocoa
drivers an entry for bind_mouse_buttons reached master before the one
that added the member to struct input_driver. gcc and clang only warn
(excess elements in struct initializer), and no Linux job compiles the
UWP driver at all. Master has the member now, and the build with it.

tools/input_driver_table_check.py counts the entries of every input
driver's table in the source - down each side of any #if in it - and
fails a table with more than the structure has members, whatever SDK
the driver needs. Against the header as it was when the build failed
it names those four tables. It runs in the driver vtables job.
2026-10-06 15:44:27 +02:00
LibretroAdmin a7999b032c input: one table for the stick-drives-the-D-pad modes, where five switches said it
What a "stick drives the D-pad" mode comes to was worked out by five
switches in input_driver.c: three, word for word the same, turned a
forced mode into its plain one and dropped a plain one when the core
had asked for analog input; two more, the same again, said which stick
a mode had taken. The modes keep the numbers the settings files have
for them, so none of it can be read off the number.

A nine-row table says it once - the sticks a mode takes, whether it is
forced, the mode it acts as - with the modes' order held at compile
time, and input_analog_dpad_mode_in_effect() and
INPUT_ANALOG_DPAD_TAKES_STICK() read it. Two of the three run for each
port each frame, and the stick test on every read of a stick.

Checked against the switches as they were for every mode, every stick
index and both answers of whether the core asked for analog input: 400
cases, none differ.
2026-10-06 15:17:46 +02:00
LibretroAdmin f5764d1380 input: a stick's binds are one number, not pairs looked up
The binds behind a stick's axes are numbered in a row - the left
stick's x then y, the right stick's x then y, and for each axis the
bind for one way then the bind for the other. The macro that mapped an
axis to its two binds went through a switch and filled two variables,
and the two pad functions that read a stick kept six ids and six
pointers from three or two passes through it.

One number names an axis's two binds (the - bind is the one after it),
and a stick's four are in a row from its x, so:
- the switch is arithmetic (INPUT_ANALOG_AXIS_BIND), with the numbering
  it rests on held at compile time;
- input_joypad_analog_axis() keeps two ids and two pointers,
  input_joypad_analog_stick() one of each;
- a stick's two keys are read as two bits side by side in the
  frontend's key word: one load for both ways.

An index or an axis that is not a stick's now returns at once. It read
the first bind for both ways and both axes before, which cancelled to
the same nothing.

tools/input_state_grab_check.py goes by the new name as well as the
old.
2026-10-06 15:11:47 +02:00
sonninnos 501990e6c1 Scanline Sync refactor part 2 (#19710) 2026-10-06 16:11:00 +03:00
LibretroAdmin 2d8dcb8dcc input: a driver that keeps its mice can say what a port's is holding
A control bound to a mouse button is read by the frontend from the mice
a driver publishes. Three drivers that are about to give the frontend
their keys - udev, UWP, the web - let a control be bound to a mouse
button and do not publish their mice. bind_mouse_buttons in the driver
table lets such a driver say what the mouse of a port is holding, as
INPUT_POINTER_* bits: which mouse a port has, and when it counts, stay
the driver's own rule. It is asked once a poll, and only of a port
with such a bind. No driver has it yet; a driver without it reads the
published mice as before.
2026-10-06 15:04:25 +02:00
LibretroAdmin 34b38b9a0d input: the frontend answers the lightgun's buttons from their binds
The X11, raw input, DirectInput and SDL3 drivers each read the
lightgun's buttons from what they are bound to - a pad's button or
axis, then a key, then a mouse button - in fifty lines of their own.
The frontend does it, from the keys and the mouse it already has for
the port, and the four copies are deleted. A driver says its lightgun
buttons are bound ones when it publishes its mice
(INPUT_POINTERS_GUN_BUTTONS_BOUND); the Wayland and SDL1 and 2 drivers
do not, and go on giving the lightgun the mouse's buttons themselves.

Which mouse a lightgun's button reads is kept as each driver had it:
the port's as the driver lists them - the one at its Mouse Index (raw
input, DirectInput), or the one there is (X11) - and with SDL3 the one
mouse whatever the port (INPUT_POINTERS_GUN_BUTTONS_EVERY_PORT).

One thing changes, under raw input only: a lightgun button bound to a
key was read whether or not the window had the keyboard, where every
other key a port's controls are bound to is not read in the
background. It follows the same rule now.

A scripted session - the trigger on a mouse button, four buttons on
keys, two ports, a key with the mouse button - reads the same in a
core before and after with X11, raw input, DirectInput and SDL3.
2026-10-06 15:04:21 +02:00
LibretroAdmin 29a4376a07 input: the stick-axis-to-binds macro moves into input_driver.c
input_conv_analog_id_to_bind_id() was in input_driver.h for the input
drivers, each of which worked a stick out from the keys bound to it.
None does now - the frontend answers it - so the macro is the
frontend's alone and moves into input_driver.c.

tools/input_state_grab_check.py fails any other file that uses it.
2026-10-06 15:02:25 +02:00
LibretroAdmin 2f8639fd00 rwebinput: the frontend answers the controls bound to keys and mouse buttons
The web driver gives the frontend its keys (keys_down) and what the
mouse is holding (bind_mouse_buttons), and loses its own code for
RETRO_DEVICE_JOYPAD and RETRO_DEVICE_ANALOG. Two rules of its own are
kept: the mouse's buttons count for the first port only, and a key
bound to a stick's direction does not count while the keyboard is the
core's.

One thing goes: a stick's direction bound to a mouse button, which
this driver alone read (and for the wrong port: it passed the stick's
index where the port was wanted).

Compiled here against the Emscripten headers; not run.
2026-10-06 15:02:21 +02:00
LibretroAdmin 5fb3f6f0b6 uwp: the frontend answers the controls bound to keys and mouse buttons
The UWP driver gives the frontend its keys (keys_down) and what the
mouse is holding (bind_mouse_buttons), from the same two calls into
uwp_main.cpp it used, and loses its own code for RETRO_DEVICE_JOYPAD
and RETRO_DEVICE_ANALOG.

Not built for UWP here. The file is plain C and compiles clean with
mingw, as gnu99 with warnings and as C89; uwp_main.cpp is not touched.
2026-10-06 15:02:17 +02:00
LibretroAdmin 8c961c8c26 udev: the frontend answers the controls bound to keys and mouse buttons
The udev driver gives the frontend its keys (keys_down) and loses its
own code for RETRO_DEVICE_JOYPAD and RETRO_DEVICE_ANALOG. A port's
controls answer to the one keyboard the port was given, if it was given
one, and a hotkey to every keyboard: its own rule, kept in its
keys_down.

It keeps its mice to itself, so it says what a port's mouse is holding
for the controls bound to its buttons (bind_mouse_buttons), by the rule
it had: the port's mouse, and none while the window is without focus.

Compiled here and built into the Linux binary; not run - it needs real
input devices.
2026-10-06 15:02:14 +02:00
LibretroAdmin 8370eee080 cocoa: the frontend answers the controls bound to keys
The Cocoa driver gives the frontend its keys (keys_down) and loses its
own code for RETRO_DEVICE_JOYPAD and RETRO_DEVICE_ANALOG. It never read
a control bound to a mouse button, and does not publish a mouse, so it
still does not.

Not compiled or run here: there is no Apple toolchain. The change is
the same shape as the drivers that were.
2026-10-06 15:02:10 +02:00
LibretroAdmin 20ee7616e6 linuxraw: the frontend answers the controls bound to keys
The linuxraw driver gives the frontend its keys (keys_down) and loses
its own code for RETRO_DEVICE_JOYPAD and RETRO_DEVICE_ANALOG, as the
X11, Wayland, Windows and SDL drivers did. It has no mouse.

Compiled here; not run - it needs a real terminal.
2026-10-06 15:02:06 +02:00
Davey HughesandLibretroAdmin 36757a089b wasapi: a microphone on a stereo endpoint opens shared (#19724)
* wasapi: a microphone on a stereo endpoint opens shared

The microphone is opened as a mono stream. On a capture endpoint whose
mix format is stereo the shared engine has no such stream to offer, and
IsFormatSupported answers with the mix format. Since ec6807994d a
suggestion with another channel count is a refusal, so the open fell
through to exclusive mode: the device was taken from every other
program, or the microphone did not open at all where the device has no
mono exclusive format. Before that commit the stereo suggestion was
taken and read as mono.

Ask the engine to convert instead (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM):
the stream opens shared and mono, at the rate and sample format asked
for. IAudioClient3's periods are for the engine's own format, so a
converted stream goes straight to the legacy Initialize.

samples/audio/wasapi_mic: the shared fake WASAPI gains a mix format,
and a mono request on a stereo endpoint opens shared through the
converter while a wider one is still refused.

* wasapi_mic_test: a converted microphone keeps the core's rate

The stereo endpoint is opened at 32730 Hz as well, a rate its engine
does not run at: the stream opens shared through the converter and
keeps the rate the core asked for.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-06 14:11:04 +02:00
LibretroAdmin e7b5af201e wasapi samples: the fake WASAPI builds and runs on Windows
Under MinGW the fake takes the SDK's own types and constants, and the
Win32 calls it answers are renamed so the driver's text binds to them
while the libretro-common units beside it keep kernel32's. The device
clock and the pacing test sleep with retro_sleep_until_us(), which is
clock_nanosleep() on Linux as before and a high-resolution wait on
Windows, where a pthreads sleep keeps the scheduler tick. wasapi_mic
and wasapi_exclusive_pacing link rtime.c, which carries retro_sleep_us
on Windows.
2026-10-06 14:07:09 +02:00
LibretroAdmin 9cb2a30fcf memmap (uwp): declare MapViewOfFileEx where the headers leave it out
The UWP build warns C4013 at both calls to MapViewOfFileEx in
memmap.c: its headers do not declare the function, so C takes it to
return int, and on a 64-bit build the address it returns is cut to its
lower half before memshm_map() hands it on. The build links, so the
function is there; it is declared as the desktop headers declare it,
under the app family only.

Not built with MSVC for UWP here. With mingw set to the app family
the declaration compiles and the call is no longer undeclared; desktop
Windows and Linux do not see it.
2026-10-06 13:45:53 +02:00
LibretroAdmin d9ab63cd37 vfs (uwp): define retro_vfs_file_prefetch_impl
The read-ahead hint added retro_vfs_file_prefetch_impl() to
vfs_implementation.c and a call to it from file_stream.c, and not to
vfs_implementation_uwp.cpp, which replaces the C backend on UWP: the
UWP build failed to link (LNK2001), and the VFS backend parity check
failed with it.

The UWP backend never maps a file and its handles come from the app's
brokered file calls, which have no read-ahead request, so the function
does nothing there - the documented answer where the OS has none.
2026-10-06 13:45:50 +02:00
LibretroAdmin 34ae42cf95 input: the frontend answers a stick's axes from the keys bound to them
The seven drivers that give the frontend their keys each still worked
out RETRO_DEVICE_ANALOG themselves, in the same thirty lines: a key
bound to one way of a stick's axis reads all the way that way, to the
other the other, and both held read neither. The frontend answers it
from the keys it already has for the port - no call into the driver -
and the seven copies are deleted.

Raw input's rule that a port's controls answer to the port's own
keyboard, and not at all in the background while the menu is up,
holds as before: it is in its keys_down, and the stick's binds are a
port's controls.

A scripted keyboard session - each way of a stick, both ways at once,
the other stick, a stick with a pad key - reads the same in a core
before and after.
2026-10-06 13:42:24 +02:00
LibretroAdmin b5cb5e54ed input: Wayland, raw input, DirectInput and SDL give the frontend their keys
The Wayland, Windows raw input, DirectInput, SDL1, SDL2 and SDL3
drivers each get keys_down - which of a list of keys are down - and
lose their own code for RETRO_DEVICE_JOYPAD, as the X11 driver did: the
frontend answers the RetroPad's buttons and the hotkeys bound to keys
from one call a port a poll.

keys_down is given the bind each key is for. Raw input needs it: a
port's controls answer to the one keyboard the port was given, a hotkey
to every keyboard, and in the background keys play the game only. That
is its own rule and stays in its keys_down.

Which mouse a port's button binds read follows how the driver lists
its mice: where it lists them (raw input, DirectInput, SDL), the one at
the port's Mouse Index; where it has the one (X11, Wayland), the port
whose index is 0.

One thing changes: Wayland, SDL1, SDL2 and SDL3 never read a control
bound to a mouse button - Wayland's code said TODO - and now do, as
X11 and the Windows drivers always have.

A scripted keyboard session - each pad key, a three-key chord, a bind
to a mouse button, the reset and pause hotkeys - reads the same in a
core before and after with raw input and DirectInput under Wine and
with X11; with Wayland, SDL2 and SDL3 it differs only in the mouse
button's bind being read.

winraw_keyboards_test read the RetroPad from the driver's state
function, which no longer answers for it; it asks winraw_keys_down()
now, a bind at a time and a port's sixteen at once, and holds what it
held: a port's controls on its own keyboard, a hotkey on any.
2026-10-06 13:00:13 +02:00
LibretroAdmin c771d5bf7c vfs: a read-ahead hint for a range about to be read
retro_vfs_file_prefetch_impl() and filestream_prefetch() ask the OS to
start bringing a byte range in before the reads that follow: on a mapped
file through madvise(MADV_WILLNEED), or PrefetchVirtualMemory() where
Windows has it, resolved by name; on a descriptor through
posix_fadvise(WILLNEED). A hint and nothing more - it returns at once,
does nothing on a frontend VFS or a non-file scheme, and changes no
bytes - so a consumer that knows its next range (a disc reader handed a
sector count) can take the disk wait off the thread doing the reading.

vfs_prefetch_test runs every shape of range through the mapped and the
descriptor paths and checks the frontend VFS is never called.
2026-10-06 12:54:20 +02:00
Davey HughesandLibretroAdmin 828181c8be vulkan: keep HDR support when a cached instance is reused (#19720)
* vulkan: keep HDR support when a cached instance is reused

Cores that cache their Vulkan context (Azahar's hw_render.cache_context)
skip vulkan_context_create_instance_wrapper() on a reinit, so the new
context struct never learns whether VK_EXT_swapchain_colorspace was
enabled. HDR support silently drops after any reinit (fullscreen
toggle, resolution change) while such a core is loaded.

Store the instance's HDR capability alongside the other cached-instance
statics and restore it when the instance is reused.

* threaded_video harness: a core that caches its context

HARNESS_CORE_HW_VULKAN=3 makes the harness core cache its context
across a video reinit, as Azahar does, and a lane holds what the
reinit must carry over with the reused instance: HDR support stays
set, context_reset is not called, and the core's image survives. The
ICD need not offer the colorspace extension, since the lane sets the
flag itself and checks the carry-over rather than the probe.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-06 12:45:54 +02:00
LibretroAdmin 388e0d12ba rzip_archive: a zip reader over bytes, the zip twin of r7z_archive
rzip_archive parses a zip's central directory - zip64 records and
extra fields included - from the archive held in memory or through a
positioned-read callback, and hands members out either as a view into
the memory (a stored member, in place) or decoded into a buffer of
their own, checked against the directory CRC. It depends on nothing
but the deflate and CRC encoders, so a core can take it for a mapped
archive without the archive_file extraction framework.

rzip_archive_test runs embedded stored, deflated, zip64 and commented
archives through both supply paths. The r7z sample links rtime.c so
it builds under mingw-w64.
2026-10-06 12:26:04 +02:00
LibretroAdmin 9f724f2082 input: the frontend answers key binds from one driver call a poll; x11 does
Every input driver works out for itself which RetroPad buttons and
hotkeys are down from the keys bound to them, in code of its own, and
the frontend called it once a bind: a frame's hotkeys alone were some
eighty calls into the driver.

A driver can now give the frontend one thing instead - which of a list
of keys are down (keys_down in the driver table). The frontend keeps
the keys a port's binds name, compiled only when the binds change
(the change count's first reader), asks for them all once a poll, and
answers every RetroPad button and hotkey bound to a key from that: a
query is a test of one bit, the RetroPad's sixteen are a ready mask,
and a bind with no mouse button never looks at the mouse. A driver
without keys_down is asked a bind at a time, as before.

The X11 driver has it, and its own code for RETRO_DEVICE_JOYPAD is
deleted; keys bound to an analog stick and the lightgun's buttons are
still its own. With a core running under X11:
- calls into the driver's state function over 300 frames: 26003
  before, 730 now;
- instructions executed in the frame's hotkey and button evaluation,
  counted by callgrind: 6527149 before, 5906701 now, 9.5% fewer.
A scripted keyboard session - each pad key, a three-key chord, a bind
to a mouse button, the reset and pause hotkeys - reads the same in a
core before and after.
2026-10-06 12:07:23 +02:00
Davey HughesandLibretroAdmin 0c3bfcc6ae video_thread_hw: keep a Vulkan set_image core within the sync rules (#19723)
* video_thread_hw: keep a Vulkan set_image core within the sync rules

Under threaded video, a Vulkan core that hands over its own images with
set_image broke Vulkan's synchronisation rules:

 - a frame replaced before the video thread claimed it left its
   semaphore signalled, and the core's next submit signalled it again
   (VUID-vkQueueSubmit-pSignalSemaphores-00067);
 - the core could render into an image a queued frame had still to
   read;
 - a dupe ran the slot's command buffers a second time, and a software
   frame read the last image unfenced.

The core now gets the slot that is neither the one just published nor
the one last presented, and takes back a frame the video thread has not
claimed rather than wait for it: its semaphores are waited on the queue
behind the slot's fence, and it counts as dropped. A frame that reads a
slot again takes only its image. The core never waits for the
presenter, only for the queue to reach a taken-back frame's waits.

Not covered: frames that fast-forward's frame skip drops before the
video driver, and a core that signals one semaphore for every frame
instead of one per sync index.

vulkan_set_command_buffers no longer passes a null source to memcpy.

The threaded_video harness core's Vulkan hardware mode gains a ring
mode and a lane for it. Without this change the validation layer
reports 00067 for the frames it replaces; with the taken-back frame's
waits left unfenced, 05149 when the core recycles a semaphore. The mode
runs on the ASan Vulkan leg as well.

* video_thread_hw: a dropped frame costs one queue submission

The submission that waits a taken-back frame's semaphores carries the
slot's fence itself, through the fence protocol's own signal hook, so
a drop is one vkQueueSubmit under the queue lock rather than one per
semaphore and another for the fence. The per-semaphore wait stages
live in the slot, grown with its semaphores.

Two instrument counters, hw_drop and hw_drop_submit, record drops and
what they cost; the threaded_video harness's hw ring sync lane holds
them equal and to the frames it saw replaced.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-06 11:37:37 +02:00
LibretroAdmin 2d66ebae8a r7z_archive: borrow a member out of the decoded folder
r7z_archive_entry_borrow() returns the member's bytes inside the
folder cache, decoding the folder first when it is not the one held,
with the CRC checked. The pointer belongs to the archive and holds
until another folder is decoded, that folder is detached, or the
archive is closed; a solid archive is one folder, so a consumer can
hold every member of it at once for one decode and no copies.
2026-10-06 11:19:22 +02:00
LibretroAdmin c94f384d71 r7z_archive: hand over a whole-folder member without copying
r7z_archive_extract_detach() extracts like r7z_archive_extract(), except
that a member which is its folder's entire output - every member of a
non-solid archive - is returned as the decoded folder buffer itself and
dropped from the folder cache, so a consumer that keeps the member for
its lifetime pays one decode and one allocation rather than a decode,
a copy and a cached duplicate. Any other member extracts as before.

r7z_archive_detach_test checks both shapes against embedded non-solid
and solid archives, including a plain extract after a detach of the
same member.
2026-10-06 11:04:15 +02:00
LibretroAdmin f485eedc70 input: a count of the changes to what a port's mapping is made from
A mapping compiled from the binds has to be made again when they
change, and nothing said when that was: a bind could be written from
the menu, the configuration, autoconfig or a remap, through a pointer,
with no trace. input_binds_generation counts each change - to the
users' binds, to the binds a pad's autoconfig profile gave it, to
which pad or kind of device a port has, to the remaps - so what is
compiled from them can keep the count it was made at and compare one
word.

What counts a change:
- input_config_bind_edit() and input_autoconf_bind_edit(), which hand
  out a bind to be written. input_config_bind() and
  input_autoconf_bind() now return a pointer to const, so the compiler
  found every writer outside input/: the menu's bind capture and its
  bind settings, and three places in the configuration code;
- the input code's setters for a port's pad and device, the remap
  functions, and the configuration's load of a file and of a remap;
- both autoconfig setters, again once they have written, as they can
  run on a task's thread;
- every frame the menu is open, which covers what its settings write
  through pointers they keep.
Nothing reads the count yet, and a frame in which nothing changes
costs nothing.

Frontend harness lane "binds change count": each of those is counted,
reading is not, idle frames are not, frames with the menu open are.
2026-10-06 10:46:14 +02:00
LibretroAdmin 651aa04cc3 input: the menu, the configuration and the UI get at binds through calls
The two bind arrays - each user's binds, and what a pad's autoconfig
profile gave it - were indexed directly from eight files outside
input/: the configuration code, five menu files, and the Qt and Win32
UI. They go through input_config_bind() and input_autoconf_bind() now,
and a whole set is copied out and back with
input_config_binds_copy_out() and _copy_in(). The arrays are the input
code's own, so how the binds are kept can change without those files
changing with it - which is what replacing them with a compiled
mapping needs first.

The calls are inline and compile to the indexing they stand for: the
six C files come out the same number of instructions, differing only
where an index that was sign-extended is now zero-extended.

tools/input_state_grab_check.py fails any file outside input/ that
names either array.
2026-10-06 10:25:45 +02:00
LibretroAdmin a24762c855 CI: the libretro-common job gets 30 minutes; the stack check compiles in parallel
The libretro-common tests job was cancelled at its fifteen-minute
limit while running its last step, the stack frame check. Nothing
hung: the job's suites, the companion harnesses under mingw and Wine
and that check have each grown with the tree.

The limit goes to thirty minutes. The stack check was also the one
step doing its work one file at a time: it compiles every shipped
libretro-common source once a toolchain, some two hundred a toolchain,
each a compiler process of its own, in turn. It runs as many at once
as there are processors, each compile with its own output files,
removed once read. The results are taken in the walk's order, so what
it reports, and which named file an error is about, are as they were.
2026-10-06 10:10:28 +02:00
LibretroAdmin 015298c2e3 input: a touch's presence is its own thing, not a position that is not 0
The pointer store took a touch to be there when its packed position
was not 0. A contact at the window's origin packs to 0, so a finger
down at 0,0 was not a touch: the pointer read the mouse and was not
pressed, and a lightgun aiming at the first touch aimed at the mouse.
DirectInput, raw input and SDL3 went by whether the contact existed
before they published, so for them this was a regression.

Where a contact is, whether there is one and whether it is down are
three things, and input_driver_publish_touches() takes them
separately: a position, a bit in @present, a bit in @down. A contact
at 0,0 is one; one that has lifted may keep its place, there and not
down - a finger's last place under Wayland, a pen in the air under
SDL3. DirectInput, raw input and SDL3's fingers say each contact they
list is there. Wayland keeps the rule it had, which is its own and is
now written where it belongs: a place at 0,0 is how the desktop reports
a mouse button, and is not a touch.

pointer_frame_test: a touch down at the origin, one lifted there, a
place with none, the viewport's edges, sparse places, the mouse's
fallback, the lightgun's choice, a pen in the air at the origin.
2026-10-06 09:48:49 +02:00
Ryunam c4049b4716 Run-Ahead: Fix intrusive guard by making it optional and improving metrics (#19716) 2026-10-06 09:50:44 +02:00
libretroadmin 997e5475be task_queue samples: build and run for Windows
retro_sleep() is a function in time/rtime.c on Win32 rather than a
macro, so five of the task_queue samples that call it compile with
mingw-w64 and then fail to link, which nothing noticed: the samples
workflow runs them on Linux only. They now link rtime.c, as the
snapshot test already did, and clean up the .exe too.

A new job in the samples workflow cross-builds every task_queue sample
with mingw-w64 and runs it under Wine, so a sample that only links on
Linux fails CI, and the threaded queue's Win32 paths in rthreads.c get
run somewhere.
2026-10-06 09:45:37 +02:00
davey a1f50fab58 task_queue: a switch to the threaded queue counts the tasks it takes over
task_queue_check() switches implementations when the threaded flag
changes, as Threaded Data Runloop does at runtime. The regular queue
leaves its tasks on the shared lists and the threaded one takes them
over, but its running count started at zero and counted only tasks
pushed to it: each inherited task took the count one below zero when
it finished. The gather sizes its report list from that count plus
eight, so with nine or more inherited tasks the unsigned size wrapped
and every check asked realloc() for about 32 GiB. A release build
gets NULL back and carries on, never again taking the idle early-out;
ASan and TSan abort. The threaded_video harness's hardware-core mode
hits it on the CI runners, where an input autoconfig task is queued at
the switch. The GCD queue took the tasks over the same way.

Both now start their counts from the lists, before a worker can touch
them. task_queue_switch_test queues sixteen tasks on the regular queue,
switches, and fails on any report list sized past a megabyte.
2026-10-06 08:39:54 +02:00
LibretroAdmin da7bac7b28 menu: the pointer positions the menu keeps are packed
The menu's pointer is one packed word, but what was kept beside it to
compare it with was still pairs: the mouse's last position, the
touch's last position, a drag's start and last positions (menu core),
Ozone's old cursor position and Material UI's drag start. Each is one
VIDEO_POS_PACK word. Whether the mouse or a touch has moved, and
Ozone's whether a touch has, are one compare where they were two, and
a position is kept by copying the word.
2026-10-06 08:09:45 +02:00
LibretroAdmin 991b1b996a input: drivers that place their own touches use the poll's viewport
The drivers the frontend does not answer for place a touch or a mouse
in the viewport themselves, and each asked the video driver for the
viewport every time: GameCube/Wii, PS3, PSP and the web on every read
of a pointer or lightgun value; Cocoa twice a touch a poll and once a
lightgun read; Switch once a touch a poll; Android and QNX once or
twice a touch event.

The frontend already keeps the viewport once a poll for the drivers
that publish. input_driver_poll_viewport() gives it to the others, with
two macros that take the place of video_driver_translate_coord_
viewport_wrap() and its confined twin, and those eight drivers use
them: one viewport a poll, however many touches and reads. How each
places its touches is unchanged. On the frontend's thread only, which
is where all eight do it; UWP, which may not, is left as it was.

pointer_frame_test checks the viewport is asked for once a poll and
afresh at the next.
2026-10-06 07:43:44 +02:00
LibretroAdmin 1595b38518 udev: the viewport is asked for once a poll
A udev mouse asked the video driver for the viewport on every relative
axis event - some thirty times a frame from a 1000 Hz mouse - on every
read of its motion, on every read of a button (to tell whether the
pointer was off the window), twice on every read of it as a pointer or
a lightgun, and three times a mouse in the poll; the touch code asked
once a device and again for every touch it placed. It is asked for
once, at the start of the poll, and that answer serves every event and
read until the next. Nothing else changes: what is read, and how it is
scaled and placed, is as it was.
2026-10-06 07:24:45 +02:00
LibretroAdmin 56b7172dee input: the SDL3 driver publishes its mouse, fingers and pen
SDL3 hands the frontend the mouse's frame, the fingers that are down
and the pen once a poll, and its own answers for the mouse, the pointer
and the lightgun's aim are deleted. Fingers are read before the pen and
the pen before the mouse, as before.

What the store learned for it:
- how many pointers are down (RETRO_DEVICE_ID_POINTER_COUNT): the
  touches that are, else the mouse standing for one. SDL3 answered
  this; the drivers converted before it gave 0, and now give the
  count too;
- a pointer placed as the lightgun is, -0x8000 outside the viewport,
  where the other drivers hold it to the edge
  (INPUT_POINTERS_POINTER_OFFSCREEN);
- a touch read alone (INPUT_POINTERS_TOUCH_ALONE): a pen in the air is
  a pointer that is not pressed, whatever the mouse's buttons say.
A touch is placed either way once a poll, as a mouse is.

pointer_frame_test covers the count and both flags.
2026-10-06 07:24:29 +02:00
LibretroAdmin 9564297a2e input: the SDL1 and SDL2 drivers publish their mouse
Both hand the frontend the mouse's frame once a poll, and their own
answers for the mouse, the pointer and the lightgun's aim are deleted;
the lightgun's buttons, which are the mouse's, stay. The mouse is the
port's whose Mouse Index is 0 and the pointer and the lightgun are
every port's, as before.

Three things in them were wrong off webOS, and are right now that the
frontend answers:
- SDL2's wheel was kept from one wheel event to the next, so one notch
  read as held until the wheel was turned again. A notch is its
  frame's, and then gone;
- SDL2's wheel read the wrong way round: SDL's y is positive for a
  turn away from the user, and that was given as wheel down. It is set
  the right way at the event, so webOS, which swapped it back when
  read, and the rest now go the same way;
- the screen mouse, which the menu's cursor follows, gave the motion
  since the last poll where it wanted a place in the window. Only
  SDL2 on webOS gave the place. Both drivers give it now.

With a core reading the pointer, the lightgun and the mouse over a
session of moves, clicks and wheel turns, SDL2 gives the same pointer
and lightgun values as before; the wheel reads up for up, for one
frame. With the pointer at rest at 300,200 in an SDL2 window the menu
was given 0,0 for its mouse and is given 300,200.
2026-10-06 06:43:53 +02:00
LibretroAdmin 9a39c5212e winraw: an absolute report's position is scaled and packed once
An absolute report's x and y were scaled back into the RAWMOUSE the
report came in, read from it twice more for the motion, and packed at
the end. They are scaled into one VIDEO_POS_PACK word; the motion is
taken from that word and the one before it, and it is stored as it is.
2026-10-06 06:29:19 +02:00
LibretroAdmin 63bc2b3f53 winraw: a mouse's motion and wheel are one word each; drop abs_ref
Three more pairs in a raw input mouse:

- dlt_x and dlt_y, the motion the window procedure adds up and the
  poll takes, are one VIDEO_POS_PACK word: added with one
  compare-and-swap where there were two atomic adds, taken with one
  exchange where there were two, and a report's two halves are now
  taken together - the poll could take x, be passed by a report, and
  take that report's y with the frame before's x. An axis is held to
  -32768..32767 a frame, which is all a core can be given;
- whl_u and whl_d are two bits of one word: one exchange a poll;
- abs_ref_x and abs_ref_y, the window procedure's copy of the last
  absolute report, held what abs_pos already holds. The motion of an
  absolute report is taken from abs_pos, which that thread alone
  writes, and the pair is gone.
2026-10-06 06:20:36 +02:00
LibretroAdmin d51fd35081 winraw: a mouse's position is one packed word
Each raw input mouse kept its position in the window as two LONGs. It
is one VIDEO_POS_PACK word: an absolute report's word is taken as it
is, where it was unpacked and stored a half at a time; where every
mouse is given the same position it is packed once, outside the loop;
and the frame the driver publishes takes the word without repacking.
2026-10-06 06:12:22 +02:00
LibretroAdmin 1c2345f656 input: DirectInput publishes its mouse and touches
The DirectInput driver hands the frontend its mouse's frame and its
touches, in the order they went down, once a poll, and its own answers
for the mouse, the pointer and the lightgun's aim are deleted.

Two things DirectInput does that the store now does for it, each a
flag a driver sets:
- the mouse is the port's whose Mouse Index is 0, while the pointer and
  the lightgun's aim are every port's (INPUT_POINTERS_AIM_EVERY_PORT);
- the lightgun aims where the first touch is while there is one
  (INPUT_POINTERS_GUN_AT_TOUCH), placed once a poll.

A wheel notch was kept until something read it, and the first read
used it up. It is the frame's now, as with the other drivers: cleared
at the next poll, and the same for every reader, a control bound to
the wheel included.

pointer_frame_test covers both flags.
2026-10-06 06:12:18 +02:00
LibretroAdmin a3e659cfa1 Clear the MSVC build warnings
wdmks and vfs_implementation carry their CTL_CODE pieces and FSCTL
codes under RA_ names, so a unity build that also includes winioctl.h
holds one definition of each. The menu/input text-type check compares
constants of one anonymous enum. rzstd shifts a size_t against the
sequence count; shader_gl3 drops three unused locals.
2026-10-06 05:59:06 +02:00
LibretroAdmin ce0e45cbee huffman: rename the small-tree member; rchd: unsigned hunk limit compare
rpcndr.h defines 'small' as a macro, so in a unity build that has
pulled in the Windows COM headers the member 'rhuff_dec_t small' no
longer parses; it is 'lens'. The hunk-size limit is compared as
uint32_t.
2026-10-06 05:56:11 +02:00
LibretroAdmin 23d7c3e202 apple: drop the libchdr header group from the iOS13 project
The project builds CHD through rchd, so the libchdr header references
are gone from the navigator.
2026-10-06 05:45:13 +02:00
LibretroAdmin 69fb7bf797 ffmpeg core: C89
The run-loop audio buffer lives in the core state, allocated with the
decode ring at the stream's sample rate and freed with it; the
resampler array is sized by MAX_STREAMS; loop counters are declared at
block scope.
2026-10-06 05:45:13 +02:00
LibretroAdmin d99fdd1605 tvOS: build without GCC_FAST_MATH
The two tvOS configurations compile the same sources as iOS and macOS
with IEEE semantics intact, so code that classifies NaN or infinity
behaves the same on every Apple target.
2026-10-06 05:45:13 +02:00
LibretroAdmin 979465594f input: raw input publishes its mice and touches; ports by Mouse Index
The Windows raw input driver hands the frontend each mouse's frame and
its touches once a poll, and its own answers for the mouse, the pointer
and the lightgun's aim are deleted. Read by a core over a session of
moves, clicks and wheel turns under Wine it gives the same values as
before, line for line.

Three things the store needed for it:
- a port reads the mouse at its Mouse Index, or where its pinned mouse
  is (INPUT_POINTERS_BY_MOUSE_INDEX): which mouse a port reads is
  decided in the frontend, once, and no longer looked up by the driver
  on every value;
- a mouse standing for one touch, its left button, as raw input had it.
  The third argument of input_driver_publish_pointers() is flags now:
  that, or three touches as X11 and Wayland have it;
- raw input takes a mouse's reports differently when it is read as a
  pointer or as a lightgun. The frontend records what each published
  device was last read as, and the driver asks at its poll
  (input_driver_pointer_read_as()).
A port with no mouse still reads a real touch.

pointer_frame_test covers reading by Mouse Index, a port with no mouse,
and the record of what a device was read as. The raw input samples get
stand-ins for the three calls.
2026-10-06 05:43:56 +02:00
LibretroAdmin 59fa8b8cf0 uwp: build rflac
HAVE_RFLAC joins HAVE_RCHD so FLAC-compressed CHD hunks decode and the
audio mixer plays FLAC, as on the configure-based Windows build. rflac.c
is already part of the griffin build through HAVE_CHD.
2026-10-06 05:40:22 +02:00
LibretroAdmin d580ffc2f7 android: the legacy NativeActivity project builds rzstd
HAVE_RZSTD joins HAVE_RCHD so zstd-compressed CHD hunks decode and the
rzstd transform stream is available, as in Android.mk.
2026-10-06 05:39:32 +02:00
LibretroAdmin ad19e0d6a3 msvc: build CHD support into the desktop projects
Every desktop MSVC project (VC6 through 2022) defines HAVE_CHD and
HAVE_RCHD, with HAVE_RFLAC and HAVE_RZSTD so FLAC- and zstd-compressed
hunks decode, matching the configure defaults. Griffin supplies the
codecs rchd calls: deflate always, r7z_lzma through HAVE_7ZIP, rflac
with HAVE_CHD, rzstd and huffman through their defines.
2026-10-06 05:38:20 +02:00
LibretroAdmin 7ac3f16060 Move the remaining platforms to rchd
Emscripten, RS90, Android (Android.mk and the legacy NativeActivity
project) and UWP define HAVE_RCHD with HAVE_CHD, so every build reads
CHD images through formats/chd/rchd.c and griffin leaves libchdr out.
Each platform already builds the codecs rchd calls for the formats it
had before.
2026-10-06 05:29:23 +02:00
LibretroAdmin d2a4956247 apple: legacy projects build rzstd
RetroArch.xcodeproj and RetroArch_OSX107.xcodeproj define HAVE_RZSTD,
so rchd opens zstd-compressed hunks and the rzstd transform stream is
available, as in the xcconfig-based projects.
2026-10-06 05:21:09 +02:00
LibretroAdmin 19894fbcdc apple: read CHD images with rchd
The Xcode flag sets gain HAVE_RCHD alongside HAVE_CHD, so griffin takes
the built-in reader arm (encoding_huffman + formats/chd/rchd.c) and
none of libchdr is compiled, as every Makefile platform already does.
Deflate, rzstd, rflac and r7z_lzma are already part of the Apple build.
2026-10-06 05:19:08 +02:00
LibretroAdmin 78837085bb threaded_video harness: read the X11 mouse from the frontend; null audio is no error
The X11 driver publishes its mouse at its poll and no longer answers
for it itself, and two lanes of the threaded-video harness still asked
the driver: "x11 event pump" never saw a wheel notch and "x11 grabbed
mouse" read no motion, which failed the Vulkan, GL and D3D11 harness
jobs. They read through the frontend now, as a core does
(input_driver_device_state(), input_driver_device_pos()).

Starting the audio drivers with the null driver chosen logged "Failed
to initialize audio driver. Will continue without audio." as an error
every time - 62 times in one harness run, and in every test that runs
without sound. The null driver was asked for and has nothing to
start: it is no longer reported. A driver that is really missing still
is.
2026-10-06 05:15:56 +02:00
LibretroAdmin e7272d1273 Clear clang warnings in the gfx, menu, record, camera and Apple code
gfx_display: GFX_DISPLAY_STAT_FLUSH_IDX() indexes the flush counters
without arithmetic between two enum types.

gl2 and the recorder size their readback buffers in size_t, matching
VIDEO_SCALE_AREA. The remaining -Wshorten-64-to-32 sites (rgui, xmb,
ozone, widgets, menu_cbs_ok, the ffmpeg camera and core) cast at the
point the value enters a narrower parameter or field, and xmb/ozone
keep their horizontal-list offset in size_t. Two unused shader
parameter locals and an unused netplay 'user' local are dropped.

apple_runtime_os_version() is inline, as GCDWebServer includes the
header on targets where no caller remains.
2026-10-06 05:04:50 +02:00
LibretroAdmin 3d67ce90fd libretro-common: clear clang 64-to-32 and unused-function warnings
libchdr: core_fread results are kept in size_t, hunk numbers derived
from 64-bit map offsets are cast at the call, and a hunk count that
cannot fit the 32-bit header field is rejected as invalid data. The
V3/V4 compressed-hunk codec pointer is declared at block scope so the
unit compiles as C89 with zlib enabled.

lrc_hash: the scalar SHA rounds are compiled only when they are the
sole path or the runtime fallback, so a target whose baseline has the
instructions does not carry unreferenced functions; SHA1Digest feeds
its length through sha1_stream_update, which already chunks a size_t.

cpu_class.h: the no-source fallback is inline, as its callers are
compiled out on those platforms. chd_stream, file_list and the TLS
chain verifier cast their known-narrow values explicitly.
2026-10-06 05:04:50 +02:00
LibretroAdmin bd49b0751f rcheevos: build inf/NaN floats from their encoding, cast narrowing stores
rc_build_float assembles the special values from the IEEE bit pattern
with memcpy rather than INFINITY/NAN, which clang rejects under
-ffast-math. The remaining -Wshorten-64-to-32 sites get explicit casts:
the lowest-set-bit of a size_t disconnect mask, and a parsed game id
read with strtoul.
2026-10-06 05:04:50 +02:00
LibretroAdmin e514fef0b8 SPIRV-Cross: classify non-finite constants from their IEEE bits
The GLSL constant emitters derive +inf, -inf and NaN from the raw
16/32/64-bit encoding instead of comparing the converted value against
numeric_limits::infinity() and calling isnan/isinf, so the result holds
under -ffast-math (GCC_FAST_MATH in the Apple targets), where clang
flags every such use with -Wnan-infinity-disabled.
2026-10-06 05:04:50 +02:00
LibretroAdmin 59fa38d1b4 input: wayland publishes its mouse and touches; the store has touches
The Wayland driver hands the frontend its mouse's frame and its touches
once a poll, and its own answers for the mouse, the pointer and the
lightgun's aim are deleted; the lightgun's buttons, which are the
mouse's, stay. Read by a core over a session of moves, clicks and wheel
turns under Weston it gives the same values as before, but for a second
read of the wheel in a frame. With that core reading the pointer and
the lightgun's aim every frame, 300 frames took 2796 translations and
2798 viewport fetches before and take 600 and 302 now.

Three things change with it on Wayland. Relative motion adds up between
polls: each event used to replace the last, so only the last of a frame
counted. The motion and the wheel are taken once a poll, where the
first read cleared them. And with a real touch in a place, whether it
is off-screen goes by the touch, not by the mouse.

The frontend's store takes touches (input_driver_publish_touches()): a
place with a touch reads the touch, a place with none the mouse standing
for it. How many touches a mouse stands for is the driver's to say - 3
for X11 and Wayland, as they had it, 1 for the drivers that will take
the left button only - so input_driver_publish_pointers() gains that
argument.

The store moves to input/input_pointer_frame.h so that a test can drive
it: samples/input/pointer_frame, in the input samples job, counts the
viewport fetches and translations - eight pointer reads are one of each
- and covers touches, both ways a mouse stands for them, and the wheel.
2026-10-06 05:01:15 +02:00
LibretroAdmin 3a56705fb1 apple/fastlane: force Apple's rsync for exportArchive
xcodebuild -exportArchive copies the archive with rsync and only works
with /usr/bin/rsync. Put /usr/bin first in PATH before gym runs so a
Homebrew rsync on the build Mac cannot be picked up, which made the
iOS and tvOS App Store exports fail with "Copy failed".
2026-10-06 04:45:52 +02:00
LibretroAdmin 0d9a95568e input: a driver publishes its mice once a poll, and x11 does
The frontend asked the input driver for the mouse, the pointer and the
lightgun's aim a value at a time, and the driver fetched the viewport
and translated to it for every pointer and lightgun value asked. A
wheel notch went to whichever reader asked first.

A driver can now hand the frontend each mouse's frame - position,
motion, buttons, wheel - once, in its poll
(input_driver_publish_pointers()). The frontend answers for the mouse,
the pointer and the lightgun's aim from that, places a device in the
viewport once a poll, and gives every reader of the frame the same
wheel. A driver that publishes nothing is asked as before.

The X11 driver publishes, and its own answers for the three are
deleted. With a core reading the pointer and the lightgun's aim every
frame, 300 frames took 2708 translations and 2710 viewport fetches
before and take 607 and 302 now. What that core reads is the same
value for value over a session of moves, clicks and wheel turns, but
for a second read of the wheel in one frame, which now agrees with the
first.

The frontend harness gains a lane, pointer store; x11_input_poll
checks what the driver publishes.
2026-10-06 04:18:11 +02:00
LibretroAdmin 8d86f5c592 retro_miscellaneous.h: use <emmintrin.h> for retro_cpu_relax on MSVC
VS2005's <intrin.h> collides with <windows.h> in C++ translation
units (C2733 on _interlockedbittestandset), which breaks MSVC 2005
builds of C++ cores that include this header; VS2003 and older have no
<intrin.h> at all. Take _mm_pause from <emmintrin.h>, and emit pause
directly on pre-VS2003 x86.
2026-10-06 03:46:46 +02:00
LibretroAdmin aa50c827f7 eventcount: arbitrate on the 3DS through the svc on libctru 1.x
The buildbot's 3DS image carries libctru 1.x, which has the address
arbiter but not the syncArbitrateAddress wrappers over it, so the
backend now makes the svcArbitrateAddress call itself on that
toolchain, under the same USE_CTRULIB_2 gate rthreads.c already uses,
through a helper of its own name so the griffin unity build keeps both
units in one TU.

tools/ctr-libctru-compat-check.sh syntax-checks the 3DS arms of both
units, alone and as one TU, against real libctru 1.x and 2.x headers
with the host identity shed, so a wrapper only one libctru has is
caught before delivery.
2026-10-06 03:36:02 +02:00
LibretroAdmin 5f9088ee51 win32: the IME's window code is a header, and Wine tests it
The window's input context - and so the IME - is attached only while a
line of text is open; that code moves from win32_common.c to
gfx/common/win32_ime.h, unchanged, as win32_ime_set(), _post() and
_sync(), so that a test can drive the same code.

samples/input/win32_ime builds with mingw-w64 and runs under Wine in
the Wine input job. As RetroArch does with threaded video, the window
is made and pumped on its own thread, a line of text is opened and
closed from another, and the window is asked on its own thread whether
it has an input context: none when made with no line open, one once a
line is opened, none once it is closed, one when made while a line is
open, and closing a closed line posts nothing. With the header changed
to keep the context always, the test fails.
2026-10-06 03:02:37 +02:00
LibretroAdmin 1baadb09e5 input drivers: the mouse pairs of nine drivers are packed
Each of these was a pair of ints, written and read one axis at a time;
each is now one VIDEO_POS_PACK word:
- DirectInput: the mouse's motion and position;
- raw input: the pointer;
- SDL1 and SDL2: the mouse's motion and position, read from SDL into
  locals and stored once per poll;
- PSP and Switch: the mouse's position and motion;
- GameCube/Wii: the mouse's position and last position, read into
  locals and stored once;
- Wayland: the pointer's position, last position and motion;
- the web: the mouse's position and its two motions;
- Android: the mouse's position and motion.
A pair set together is stored once; a single axis is set with
VIDEO_POS_PUT_X/Y and a running total moved with VIDEO_POS_ADD.
2026-10-06 02:52:41 +02:00
LibretroAdmin 47b354ec69 uwp: the touch positions use the packed viewport translation
The viewport translation returns two packed positions now, and UWP's
touch handler, in C++ and so missed by that change, still passed it
four 16-bit pointers: the UWP build failed. Each touch keeps its
viewport and full-screen position as one VIDEO_POS_PACK word, passed
straight to the translation, and the pointer reads unpack them.
2026-10-06 02:48:13 +02:00
LibretroAdmin 426013a200 video, input: the viewport translation returns two packed positions
video_driver_translate_coord_viewport() returned its result through
four 16-bit outputs - x, y, screen x, screen y - so every input driver
declared four locals and passed four pointers, and some packed the
results again straight after. It returns the position and the screen
position as two VIDEO_POS_PACK words now, and its two wrappers take
the same two.

Every caller is converted: the X11, Wayland, udev, raw input,
DirectInput, SDL1/2/3, Cocoa, Android, QNX, web, PSP, PS3, Switch and
GameCube/Wii drivers. Where a driver kept the results packed, it
passes its own fields straight in (Android, QNX, Cocoa, udev, and
Switch, whose four touch arrays become two). udev's mouse pointer
helper returns a packed position too. As before, a translation that
fails leaves its outputs as they were. The samples that stand in for
the function take the new signature.
2026-10-06 02:31:08 +02:00
github-actions 4fd2697360 Fetch translations from Crowdin 2026-10-06 00:32:16 +00:00
LibretroAdmin 4a56c2f38c udev: the vertical scroll gesture adds to the vertical wheel
Whether a vertical scroll step adds to the wheel or starts it afresh
was decided by comparing the vertical scroll with the horizontal
wheel; it compares with the vertical wheel now, as the horizontal case
does with the horizontal one.
2026-10-06 01:45:16 +02:00
LibretroAdmin 8814c23eae udev: every integer x/y pair is packed
The touch slots' position and touch-down point, the touch pointer's
panel and main-panel position and its motion, the simulated mouse's
position, motion and wheel, the gesture scroll, the OS pointer and
each mouse's relative motion were pairs of 16- or 32-bit values. Each
is now one VIDEO_POS_PACK word. A pair set together is stored once;
a value the kernel reports one axis at a time is set with
VIDEO_POS_PUT_X/Y, and a running total moved with VIDEO_POS_ADD, both
added to video_defines.h next to VIDEO_POS_PACK. As with every packed
position, an axis is held to -32768..32767.

Raw absolute coordinates and axis limits stay 32-bit: a device's own
units can go past 16 bits.
2026-10-06 01:45:13 +02:00
LibretroAdmin 60a823dc8f udev: the touch pointer's positions are packed; drop the unread mouse ones
The touch pointer's screen and viewport positions were two pairs of
16-bit values; each is now one VIDEO_POS_PACK word, written once by
udev_translate_touch_pos(), which leaves both as they were when the
translation fails, as before.

The simulated mouse kept a screen and a viewport position too, and
translated them on every touchpad and trackball update and copied
them on every touch, but nothing ever read them. They go, with those
translations.
2026-10-06 01:40:08 +02:00
LibretroAdmin 7f1cde6cb4 android, qnx, sdl3 input: touch and mouse positions are packed
Android kept each touch's viewport, confined and full-screen position,
and the mouse's two viewport positions, as pairs of 16-bit values
written one at a time; QNX kept each touch's two positions the same
way; SDL3 kept its whole-pixel mouse motion as two values. Each pair
is now one VIDEO_POS_PACK word. On Android and QNX one helper does a
touch's translations into locals and stores each position once; as
before, a translation that fails for want of a viewport leaves what
was there.
2026-10-06 01:37:22 +02:00
LibretroAdmin 1e9dec8d32 input_overlay, dinput, cocoa_input: positions are packed with VIDEO_POS
The overlay kept its touch points, the pointers that missed every
hitbox, the full-screen pointer and its previous position as pairs of
16-bit coordinates, read with two calls and stored one at a time.
Each is now one VIDEO_POS_PACK word, read into it directly.

dinput.c and cocoa_input.h each had their own copy of the same
packing; both use VIDEO_POS now.
2026-10-05 17:56:20 +02:00
LibretroAdmin 16f6f1ebe9 input, menu: pointer positions are one packed word
The pointer view kept six 16-bit coordinates and was filled with six
calls; the menu's pointer state kept x, y, dx and dy apart and set and
compared each on its own.

input_driver_device_pos() reads a mouse or pointer position in one
frontend call and returns it packed with VIDEO_POS_PACK, the packing
the viewport already uses. The pointer view holds three packed
positions. menu_input_pointer_hw_state_t and menu_input_pointer_t
hold a packed position, and the pointer a packed motion; the menu
works on local x and y and stores them once, and the menu drivers
read them with VIDEO_POS_X/Y.
2026-10-05 17:24:04 +02:00
libretroadmin 5b04bb6729 memshm_area: mappings are tracked a page at a time
The area recorded at most 128 mappings, and a fastmem window maps a page
at a time - thousands of them - so every map past the 128th was refused.
It now keeps two bitmaps over its pages, allocated with it: which are
mapped, and where each mapping starts. A map is refused when it touches
a mapped page, an unmap unless it is exactly one mapping; a 4 GB window
of 4 KB pages costs 256 KB.

memshm_area_test maps 600 side-by-side regions into one area and
unmaps each.
2026-10-05 16:54:10 +02:00
LibretroAdmin 0a5678ddb8 windows_wine_smoke: a key held as the window loses focus is let go
Add a focus-loss scenario for raw input and DirectInput. B is held in
RetroArch's window, the focus goes to another window, RetroArch is
closed from outside and only then is the key released, so the core
sees B let go only if the focus change did it. Two small helpers are
built for it: one opens the other window, one posts WM_CLOSE to
RetroArch's. Without a window manager a window is given the focus by
a click in it, as the existing scenarios do.
2026-10-05 16:52:50 +02:00
LibretroAdmin e6ba0e6e2f menu: read the pointer once per poll through a frontend view
The menu called the input driver fourteen times a frame for port 0's
mouse and first touch, at whatever point of the frame it got there,
and a wheel notch - cleared by the driver when read - went to
whichever read came first.

input_driver_pointer_view() reads them from the driver the first time
it is asked after a poll and returns the same view until the next one.
The menu's mouse and touchscreen state read the view, so they agree
within a frame and a wheel notch is read once; nothing is read unless
the menu asks.

tools/input_state_grab_check.py now fails a file under menu/ that
calls input_driver_device_state().
2026-10-05 15:02:11 +02:00
libretroadmin 9e458aa262 input_overlay: a looping image's dropped frame is sent again
A frame the driver drops without uploading stays in the image's slot,
and is submitted again on the next poll before the stream moves on; the
schedule runs on as if it had been shown. Under threaded video the drop
reaches the surface's release, which marks the frame the same way, as a
two-frame image already is.

overlay_textures_test drops an animated image's frame directly and on
the stub video thread, and checks it is marked, kept off the texture,
and lands when sent again.
2026-10-05 15:00:34 +02:00
libretroadmin 90fd4df521 gfx_thumbnail_anim_test: build with mingw-w64
Regression tests must build on Windows too. Sleep with retro_sleep()
instead of usleep(), and define HAVE_MMAP and link pthread only for
non-mingw builds. CI builds the sample with mingw-w64.
2026-10-05 14:08:47 +02:00
libretroadmin 170fbc209c vulkan_validation_test: the mailbox lane sets vsync where the swapchain reads it
vulkan_create_swapchain() takes vsync from the context's swapchain
settings rather than the live settings, so the emulated mailbox lane
switches it there, as a frame would.
2026-10-05 14:08:26 +02:00
LibretroAdmin 4ccbbb4aad video: a driver's init takes no input driver slots
Every video driver's init took two slots, input_driver_t ** and
void **, to put an input driver of its own in - from when video
drivers started the input driver for their window. None does any
more: each says what kind of window it made and the frontend starts
the input driver for it. What the slots still did was carry that
call (input_driver_left_to_frontend(window, input, input_data)) and
be cleared - and with threaded video they pointed into the frontend's
input state from the video thread.

They go, from every video driver, every context's input_driver hook
and the threaded wrapper:
- video_driver_t::init is init(const video_info_t *video);
- gfx_ctx_driver_t::input_driver is input_driver(data, joypad_name);
- a driver or context says what kind of window it made with
  input_driver_video_window(kind, window_data) - window_data being
  Wayland's seat state, NULL elsewhere - which replaces
  input_driver_left_to_frontend() and _with();
- before the driver's init the frontend clears the window kind and
  the current input driver itself (input_driver_video_init_begin()),
  which every driver did through the slots; the setting's driver is
  still taken first and given back after as before.

83 files: the video drivers and contexts of every platform, the
threaded wrapper, video_driver.c and the input frontend. The calls
were changed mechanically and each changed line checked against the
expected forms; five brace-less 'if (input && input_data)' lines whose
body went with the slots were removed with it.

tools/input_video_separation_check.py fails any input_driver_t **
under gfx/; its self-test has the case.

Built for Linux (X11, GL, Vulkan, threads, networking), for Windows
with mingw-w64 (GDI, GL, D3D9-12), with overlays and networking off,
and the Android single-file build; the Wayland contexts, SDL3 and the
web contexts compiled against their headers. The frontend harness,
a run ending on the test controller's scripted quit, the X11 mouse
menu sample, the PSP driver's contract test and the Wine smoke test
pass. The console, handheld, macOS and other drivers whose SDKs are
not here were not built.
2026-10-05 14:04:46 +02:00
LibretroAdmin 8e27e634c9 thread_read_audit: context driver callbacks a frame calls are entries
The frame-context functions call their context driver through
gfx_ctx_driver_t - swap_buffers, set_resize, update_window_title,
bind_hw_render - an indirect edge the binary walk cannot follow, so
what those slots hold runs in the frame context unaudited. The tool
reads gfx_ctx_driver_t's field order from gfx/video_driver.h, finds the
function in each of those slots of every gfx_ctx_driver_t table in the
tree (positional or designated, the first arm of an #ifdef), and audits
them as frame-context entries. The self-test parses a table of both
kinds.

On a Linux build with Vulkan, GL, Wayland, DRM, SDL2 and the rest it
audits 104 entries, and on a mingw-w64 build 58, with no findings.
2026-10-05 13:37:50 +02:00
LibretroAdmin 39b49f28ff drm, wayland, video: context callbacks that run while drawing read no settings
The DRM and Wayland EGL contexts' swap_buffers run on the thread that
draws; they take max swapchain images from their context, where it is
latched when the context is made (a change of it reinitialises video).
The refresh-rate query, reached from the Wayland, X11 Vulkan and KHR
display contexts' resize and swap callbacks, picks the native or SDL
display server by video_sdl_display_server, which reinitialises nothing
when changed: the main thread publishes it with each frame's info and
when the display server is set up, and the query reads that.
2026-10-05 13:37:47 +02:00
LibretroAdmin c4aa74d708 vulkan: a swapchain made while drawing is made from the frame's settings
The thread that draws makes a swapchain when the old one is lost (the
next acquire, or a context's resize), so vulkan_create_swapchain() reads
no settings: vsync, adaptive vsync, windowed fullscreen, FSE
negotiation, HDR mode, bit depth, max swapchain images and the display
peak come from vulkan_context_t's swapchain_settings. The context seeds
them from the settings when it is made, and each frame takes them from
its video_frame_info_t, which gains vsync, adaptive_vsync and
fse_negotiation for them.
2026-10-05 13:37:30 +02:00
libretroadmin 90215cae41 threaded_video_test: the direct surface lane submits a dropped frame again
A driver with no staging free drops an update, and the frame stays in
its slot; the direct surface lane counted that as a failure where a
producer submits it again a frame later, as the lend and FP16 stream
lanes already do.
2026-10-05 13:36:30 +02:00
LibretroAdmin e0ac67e3d5 AGENTS.md: require CI regression tests to pass for covered code
Changes to code already exercised by the GitHub CI workflow regression tests must be checked against those tests, run with the workflows' own configuration, before being presented.
2026-10-05 13:35:12 +02:00
libretroadmin 7c54d590ed gfx_surface: a still updates in place again, and a dropped one is not left behind
A surface without slots goes back to updating its texture in place.
When the driver drops that update, a direct submit loads a replacement
instead, and under the wrapper the completion posts the same update
again, payload kept, until it lands: a still has no next frame to make
up for a dropped one.

threaded_video_test's lend and FP16 stream lanes submit a dropped frame
again a frame later, as a producer does, rather than count a drop as a
failure; gfx_thumbnail_anim_test checks a still updates in place, loads
on a direct drop and is posted again on a threaded one.
2026-10-05 13:25:02 +02:00
libretroadmin 2c6bbd861d samples/network: the SMB and NFS backup tests capture the settings they set
The drivers read the settings cloud_sync_capture() copied as the sync
was pushed; the tests set the destructive flag, and NFS's share, the
way the task would and capture them before each call.
2026-10-05 13:24:58 +02:00
LibretroAdmin 49a4f12e4e d3d11, d3d12: HDR metadata a frame sets takes the display's peak from the frame
A swapchain the D3D11 or D3D12 frame rebuilds sets its HDR metadata,
which with Use Display Peak on is the display's peak; that came from
video_driver_hdr_metadata_peak(), a settings read on the video thread
when video is threaded. video_frame_info_t carries the peak now, built
on the main thread, each driver keeps the frame's in its HDR state, and
dxgi_set_hdr_metadata() takes it. Driver init seeds it the way it did.

tools/thread_read_audit.py on a mingw-w64 build reported both frame
paths; it reports none with this.
2026-10-05 13:11:46 +02:00
libretroadmin d99c158b78 gfx_thumbnail_anim_test: a release that frees its surface, under the wrapper
Lane 13 completes a load and a dropped update through the stub wrapper
with a release that frees the surface, as gfx_display's texture loads
do: the completion's last touch is the release, so nothing may write
the surface after it. With the write back that followed the release,
AddressSanitizer reports the heap-use-after-free in gfx_surface_done.
2026-10-05 13:10:44 +02:00
libretroadmin 715778685c gfx_surface: a completion touches the surface no more after its release
Under the threaded wrapper the completion of a surface's upload called
its release and then cleared s->dropped, but a release may free the
surface - gfx_display's texture loads do - so every texture gfx_display
loaded through the wrapper wrote freed memory as it arrived. The
release is the completion's last touch; dropped is cleared where every
completion starts, and only a release reads it.
2026-10-05 12:54:09 +02:00
libretroadmin dae069fbe8 gfx_thumbnail: a frame the video thread's driver drops is sent again
Under threaded video a dropped in-place update comes back to the slot's
release with dropped set, and the frame is still in that slot. The
release submits it again, so the texture is not left a frame behind -
the last frame of a finished animation included - and the sibling job
waits behind it as it does behind any frame in flight. The direct path
already keeps a dropped frame for the next poll.

gfx_thumbnail_anim_test lane 11 drops an update on the stub video
thread and checks the same frame is posted again and lands; the stub's
node mirrors the wrapper's layout again, with the dropped byte.
2026-10-05 12:51:55 +02:00
libretroadmin 0e464bdf41 cloudsync: a blocking driver's calls read settings copied as the sync is pushed
The cloud_sync_capture hook copies the settings a driver's calls read,
on the main thread as the sync's task is pushed, where the task takes
its own copy too. SMB and NFS read the share, the account, the
timeouts and the destructive flag from that copy rather than from the
live settings: their calls run on a worker, read some of them after a
network call returns, and one still blocked past the exit wait would
otherwise read settings the frontend has freed.

cloudsync_async_test checks the hook runs once on the calling thread
and that a driver without one is left alone.
2026-10-05 12:51:48 +02:00
LibretroAdmin cf12b75b70 gl, glcore: take the hardware ring's fence ahead of the swap
Under threaded video a hardware rendered GL core and the video thread
wait on each other's fences. The video thread's was created after the
frame, so behind the buffer swap. A driver that queues swaps completes
such a fence only when the swap is released, at the display's rate,
which fits the report: a core fast-forwarding at 500 fps that settles
at the display's 120 once the queue has filled, where software cores,
which never wait on it, stay uncapped.

The fence is now created just before the swap, after everything that
reads the core's framebuffer, and handed over as before. Still one
fence a frame.

tools/gl_ring_fence_check.py keeps both drivers doing it.
2026-10-05 12:50:53 +02:00
libretroadmin 8c94b8f9f2 gfx: an in-place update the driver drops is reported as dropped
update_texture returns refused, done or dropped. Drivers that skip an
update rather than wait for the GPU - Vulkan, D3D10/11/12 and Metal,
when their staging is busy - return dropped instead of done; the
threaded wrap passes the outcome back as its return value and the
wrapper's async node carries it to the completion. Drops are counted
where the outcome is seen, for every driver, rather than inside three
of them.

gfx_surface_submit() returns DROPPED, the slot free and still holding
the frame. An animated preview keeps the frame for the next poll, and a
two-frame overlay image keeps its state so its press is submitted again
- under threaded video through the surface's release, which sees
s->dropped. A surface without slots always loads, so a still is never
the frame left behind. gfx_thumbnail_anim_test lane 11 drops an update
and submits a still twice against the stub driver.
2026-10-05 12:27:15 +02:00
LibretroAdmin ca0355e92e input: platform drivers ask the frontend for quit, shutdown, menu and video restart
Input drivers on several platforms acted on the frontend themselves
when the platform asked: the Wii U's panic combination quit through
command_event(); the Wii and GameCube power button, Android's system
closing the app and QNX's exit set the shutdown flag through
retroarch_ctl(); QNX's swipe down toggled the menu; Android restarted
video when its surface was lost; the web driver turned game focus off
and let the mouse go when the browser dropped pointer lock; and the
test controller's scripted quit used command_event().

They ask now, with input_driver_platform_request() and what happened:
INPUT_PLATFORM_QUIT, _SHUTDOWN, _MENU_TOGGLE, _REINIT_VIDEO and
_FOCUS_LOST. On the frontend's thread - where every one of these
drivers asks today - the frontend does it at once, as before, so
nothing about when it happens changes (Android's reinit still comes
before it tells the OS it is done). From any other thread the request
is a bit in a word the next poll takes whole, with no lock.

tools/input_state_grab_check.py fails any file under input/ but the
frontend that calls command_event(), retroarch_ctl() or
retroarch_main_quit(); there are none now.

Checked: the test controller's scripted quit ends a run, through the
new call; the frontend harness passes; the Android single-file build
compiles; the web driver compiles against the Emscripten headers; the
frontend builds with overlays and networking off, and with threads
off. The Wii U, Wii, GameCube and QNX drivers are not built here - each
change there is the one call - and none was run on its device.
2026-10-05 12:24:01 +02:00
LibretroAdmin 53937798a7 wayland/webOS: the seat's events are on the input queue there too
On other compositors the seat and what comes from it are on a queue of
their own, which only the input driver's poll dispatches
(wayland_input_dispatch()): a key or a pointer event reaches its
handler on the frontend's thread and nowhere else. webOS was left out,
as its libwayland is an older one, so there the video side's
flush_wayland_fd() - on the video thread when video is threaded - and
the poll both dispatched the one queue, and an input event was handled
on whichever got to it first.

What the input queue needs that the older library lacks is only how
the desktop code sets it up: a proxy wrapper for the relative pointer
manager, and wl_display_roundtrip_queue(). webOS makes neither: it has
no relative pointer, and its own init can do without the other. The
queue itself - wl_display_create_queue(), wl_proxy_set_queue(), reading
and dispatching one queue - it has, and where the backport is built
it falls back to the default queue on a library without the reads.

So webOS's own code now:
- creates the queue at init (RETROARCH_WAYLAND_INPUT_QUEUE=0 leaves
  the events on the default queue, as on the desktop);
- puts each seat on it in its registry handler, before anything is
  read, so the keyboard and the pointer made from it follow;
- dispatches it after the start-up roundtrips, so the seat's
  capabilities and devices are there before the window is made;
- destroys it after the seats and before the connection.
The poll's dispatch of the queue is no longer behind the desktop-only
switch (WAYLAND_HAVE_QUEUE_DISPATCH; WAYLAND_HAVE_INPUT_QUEUE keeps
the desktop set-up).

Compiled with WEBOS defined, against a stand-in for the SDK-generated
webOS shell protocol header, and the shared Wayland input code for
the desktop. Not run on a webOS TV.
2026-10-05 12:23:57 +02:00
libretroadmin 4192bd6a3d tpool: overflow nodes are reused, and each side's counters have lines of their own
A job posted with the ring full still goes on the list under the mutex,
but its node comes from the ones earlier jobs left: a taken node is kept
for reuse, up to four rings' worth, so a pool that overflows again and
again allocates only while its backlog is deeper than it has been, and
outside the lock when it does. tpool_free() frees the spares.

The ring's fill position, its empty position, and the queued and
outstanding counts each sit alone on 128 bytes, so a post and a take no
longer pull one line back and forth between them for words only one of
them changed. tpool_handoff_test stalls every worker under a backlog of
four rings, several rounds, and checks each job runs once and a repeat
round allocates no node.
2026-10-05 12:20:48 +02:00
libretroadmin c7f72914a9 cloudsync: a result past the exit wait is the worker's, and polls are paced
cloud_sync_deinit() moves the calls on to a new generation. A call made
before it that is still running when the wait gives up is disposed of by
the worker once it returns - its file closed, nothing handed back - so
its handler never runs for a task that is gone, and a sync begun later
never sees it.

cloud_sync_poll() takes the caller's budget callback and runs the oldest
finished result unconditionally, then more only while the budget allows;
the cloud sync task polls inside its shared I/O window, so a burst of
results is spread over as many runs as the window asks. NULL runs them
all. cloudsync_async_test covers the paced poll and a sync begun while an
abandoned call is still stuck.
2026-10-05 12:20:44 +02:00
libretroadmin 35b6f10232 memshm_area: map and unmap answer to the mappings the area holds
An area records each mapping as the exact range it was made over, up
to 128. A map that would touch one is refused before the OS is asked -
on POSIX that map is MAP_FIXED and would replace what is there - and an
unmap is taken only for a range that is exactly one mapping, since the
unmap writes PROT_NONE over whatever it is given and Windows unmaps a
view whole. A hole, part of a mapping or a range across several is
refused with nothing changed, and the placeholder ranges on Windows stay
in step with the views the kernel has.

memshm_area_test maps over a live mapping and across into one, unmaps a
hole and each half of a mapping, and reads the mapping back whole.
2026-10-05 12:20:40 +02:00
libretroadmin e201234dd1 gfx_thumbnail_anim_test: partial lending, and closing on lent memory
Lane 9 has the driver lend one slot and refuse the other: a two-slot
surface lends just the slot it was given and copies into the other, a
one-slot surface - which needs both to stay double buffered - lends
neither, and every frame is uploaded. Lane 10 closes animations over and
over while their jobs are queued or decoding into memory the texture
lent, with that memory freed when the texture goes; a close that let the
surface go before a running decode finished writes freed memory, which
AddressSanitizer reports. Lane 10 holds the closes to having happened
while lending, so it cannot pass vacuously.
2026-10-05 12:18:10 +02:00
libretroadmin 61b5af9500 metal: the HDR UI overlay is FP16, and shows linear scRGB textures
Under HDR every menu, overlay, OSD and font draw goes into the SDR
overlay, which the composite pass encodes onto the drawable at the menu
nits; it was BGRA8 and is RGBA16Float now, so a linear texture's values
above menu white reach the composite. A pipeline's colour format must be
its target's, and HDR is switched on at run time, so each UI pipeline -
the stock and menu states, clear, mesh, font and the menu-texture and
overlay pipelines - is made a second time for RGBA16Float by
-twinF16:, and picked while hdrEnabled; with HDR off the BGRA8 ones draw
as before.

RGBA16Float textures load from half floats and update at eight bytes a
pixel. One drawn while HDR is on goes through stock_fragment_linear,
the exact inverse of the menu composite's To2020(v^2.4) at the menu
nits for the composite's gamut, and the driver answers
TEXTURE_GPU_FORMAT_SCRGB, so an HDR video previews in HDR here as on
the other drivers.
2026-10-05 12:11:59 +02:00
libretroadmin c3323eeff6 gfx_surface: a texture outlives the lent memory a producer still writes
A thumbnail job decodes into the slot it was handed, which under
direct video may be upload memory the texture lent. When an in-place
update was refused, the surface loaded a replacement and unloaded the
old texture at once, freeing that memory while the sibling job could
still be writing it. A producer now takes its slot with
gfx_surface_slot_begin() and gives it back with gfx_surface_slot_end()
- the thumbnail when its job settles or is released - and a
replacement while a lent slot is taken keeps the old texture as
retired_handle until the last one comes back; a second replacement
before then is refused. Nothing waits.

GFX_INSTR_TEX_UPDATE_DROPPED counts the updates a driver took and
answered as done while keeping its last contents for want of free
upload memory (Vulkan, D3D12, D3D10), so instrumented runs tell an
uploaded frame from a dropped one. gfx_thumbnail_anim_test lane 8
gives textures their lent memory and replaces one under a taken slot,
deterministically: unloading it at once is a heap-use-after-free.
2026-10-05 12:04:37 +02:00
LibretroAdmin dab803e6bf task_save: a finishing save or load task hands its state to the callback
The state is the callback's task_data instead of a copy allocated when
the task finishes, so the callback always gets it: a caller of
content_save_state_notify() or content_load_state_notify() is told how
its task ended even when nothing more can be allocated by then. The
callbacks' NULL task_data branches go with the copy.

save_state_io_test wraps calloc and fails the allocations a finishing
task would make while the task runs; the notified save and load are
both told.
2026-10-05 12:04:06 +02:00
LibretroAdmin 8ebf482313 bsv_replay_init_test: lanes for the replay parsing checks that had none
Seven lanes, each failing with its check removed: a legacy checkpoint
whose size wraps leaves a seek nothing to land on, and one whose size
runs past the file is not the same timeline (skips); a truncated legacy
checkpoint leaves no size on a freed buffer (legacy_short); a replay
block shorter than a header is refused before it is read (short_block,
by ASan); a checkpoint too large to allocate records no size
(write_oom); and, with STATESTREAM, a failed decode drops the
recorder's cur_save_valid (decode_fail_invalid) and collecting an
object whose hash the map no longer holds adds nothing to the map
(index_hash_missing).
2026-10-05 12:04:03 +02:00
LibretroAdmin b33b95cbdc winraw: the window's focus is an atomic
The raw input driver keeps whether the main window has the keyboard
focus in winraw_focus. The window procedure sets it on WM_SETFOCUS and
WM_KILLFOCUS - on the video thread when video is threaded - and the
poll reads it, in eight places, to decide whether keys and the mouse
are taken. It was a plain bool written on one thread and read on
another.

It is a retro_atomic_int_t now, stored with release by the window
procedure and loaded with acquire by the poll; the eight readers are
unchanged.

The rest of what reaches input drivers from a window's thread was
looked at for the same thing, and needs nothing:
- X11: the input driver drains its own connection's events in its
  poll; focus and pointer-inside go through atomics, and so do the
  wheel notches the window loop also latches;
- Wayland: the seat's events are on a queue of their own, dispatched
  by the input driver's poll only (wayland_input_dispatch()). Not on
  webOS, whose older libwayland keeps the seat on the default queue;
- raw input reads its devices at the poll; its device-change and
  hotplug work already waits for it.

Four raw input samples that compile the driver and set its focus
(winraw_abs_pos, winraw_keyboards, winraw_mouse_hotplug, winraw_poll)
set it through the atomic now; all four pass under Wine.

Run in the Wine smoke test's raw input scenario, threaded video on:
keys, a click, a controller plugged in, a fullscreen toggle and quit,
as before.
2026-10-05 12:03:41 +02:00
LibretroAdmin 3b9eed1ed4 dinput: what the window procedure hands the driver takes no lock and frees nothing
The DirectInput driver is handed some of its input by the window
procedure (dinput_handle_message()), which runs on the thread that
owns the window - with threaded video, the video thread, not the one
that polls. What it handed over it wrote straight into the driver's
state, under the poll's feet:

- touches were a list of malloc()ed nodes, added to and freed by the
  window procedure while the poll walked it: the poll could read a
  node that had just been freed;
- the wheel's notches, a focusing click, a title-bar double click and
  the Shift and Alt keys held were bits in a flags word both threads
  read, changed and wrote back, so either could undo the other's
  change - a wheel notch lost, a focusing click let through, a
  Shift release sent twice or not at all;
- WM_MOUSEMOVE wrote two positions nothing read.

Now:

- touches are sixteen fixed slots. The window procedure, the only
  writer, fills a slot's position and order and then publishes it by
  storing its id; it frees one by storing 0. The poll reads the id
  first. Touches are given out in the order they went down, as the
  list kept them;
- the wheel, focusing click, title-bar click and the Shift and Alt
  keys are in a word of their own that both threads change only with
  atomic operations. The poll takes the wheel and click bits once, at
  its start, into its own flags, so everything after reads as it did.
  A Shift or Alt release the OS does not send is still sent by the
  poll, and a key-up the window procedure sees after that is still
  swallowed - the bit is tested and cleared in one step either side;
- the unread positions are gone.

No lock was added. Run in the Wine smoke test's DirectInput scenario,
threaded video on: keys, a click, a controller plugged in, a
fullscreen toggle and quit, as before. Touch and the wheel are not
exercised there, and there is no Windows touchscreen here.
2026-10-05 11:47:13 +02: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
Rob Loach 62d5954fbc mcp: answer tools once their work is through
A tool whose work runs over later frames answers when that work is
through, with what came of it, or with its name, ERROR and the reason:
LOAD_CONTENT, START_CORE, CLOSE_CONTENT and UNLOAD_CORE once the load or
close ends; LOAD_STATE_SLOT and SAVE_STATE_SLOT once the state is
applied or written; PLAY_REPLAY_SLOT, RECORD_REPLAY, SEEK_REPLAY and the
replay checkpoint tools once the replay has started or the seek or
checkpoint has run; SET_SHADER once the preset has compiled; AI_SERVICE
with the service's text. A line-based interface keeps the answers it
had.

The waiting is one shape, task_notify_t (tasks/task_notify.h), held by
the work's owner: the content load, the save and load tasks, the movie
tasks, the deferred shader load and the translation service. State and
replay work a command starts runs from a main-thread task, outside the
core's own input poll. The command interfaces outlive a content load,
remade only when their settings change, and what arrives during a load
waits for it.

samples/tasks/content_load gains lanes for the state-slot, replay, seek and
content replies; samples/network/mcp_server follows.

Closes #19702
2026-10-05 11:33:27 +02:00
LibretroAdmin b7638b7a88 input: the key lane keeps a release until its press is out, and its counters wrap by design
Two faults in the key lane (5925e87cc), from an outside audit, both
reproduced here.

A release could still be lost. When the lane was full a release was
kept in a bitmap, and the poll, once it found nothing more ready,
turned it into the release of a key it had seen go down - throwing it
away otherwise. Nothing more ready is not the lane being empty: a
writer that has claimed a slot and not yet filled it holds back every
slot behind it, the key's press among them. The release was thrown
away, the press came at the next poll, and the key stayed held.

A kept release now waits until its key's press has been given out, at
this poll or a later one. And so that a waiting release cannot undo a
newer press, a press takes back its key's kept release: one accepted
into the lane, or reported on the frontend's thread.

The positions and sequences were signed ints: past 2^31 events their
arithmetic overflowed, which is undefined. They are unsigned now and
wrap by design; the full-lane test reads the difference as signed, so
it holds across the wrap.

The lane moves to input/input_key_lane.h so that a test can drive the
shipping code. samples/input/key_lane includes it and checks:

- the audit's schedule: a writer stopped right after claiming a slot,
  the lane filled behind it with a key's press, the key's release not
  fitting - after the writer goes on, the press and then the release
  come out, and the key is not left held. Against the old behaviour
  this fails;
- a kept release never undoes a newer press;
- 2,000 events across the wrap of the counters, under UBSan;
- four writers of 20,000 events each, every event exactly once and in
  order, under TSan.

It runs in the samples/input job, plain, ASan + UBSan and TSan.

What the lane does not promise is said in the header: a writer stopped
between claiming a slot and filling it holds the slots behind it until
it goes on. The poll does not wait for it; those events do.

Built with overlays and networking off, and with threads off. In a
tree configured as the CI jobs are, all frontend-harness lanes pass,
the key events lane among them.
2026-10-05 11:30:48 +02:00
LibretroAdmin 1d7749fa37 CI: samples/tasks gets thirty minutes; dirwalk walks its big fixture once each under a sanitizer
The samples/tasks job has been cancelled at ten minutes, in the
dirwalk step, with nothing failing: the job runs every samples/tasks
oracle, most of them three times (plain, ASan + UBSan, TSan), 53 steps
now, and its ten-minute budget dates from when there were fewer. The
install step alone may take twenty. It gets thirty.

dirwalk_budget_test's pacing and bench lane walks its 40,000-file
fixture eight times. Under a sanitizer its wall-clock asserts are
skipped, so those rounds only check parity: it walks the fixture once
paced and once frozen there, which checks the same. Locally: plain 4 s,
ASan + UBSan 10 s, TSan 19 s, all passing.
2026-10-05 11:07:48 +02:00
libretroadmin 43bdea7c23 gl3: the scRGB menu layer is FP16, and shows linear scRGB textures
Under an scRGB backbuffer the menu is drawn into the SDR content layer,
or, for PQ content, into a UI layer of its own, and both were RGBA8:
a linear texture drawn there would be clipped at menu white, worse than
the tone map it replaces. The UI layer is RGBA16F; the content layer is
too, but while recording, which reads it back each frame
(gl3_pbo_async_readback) and keeps it RGBA8 for the copy the driver
makes without converting. TEXTURE_GPU_FORMAT_SCRGB is answered only
while the menu's layer is FP16, so a recording keeps the tone-mapped
previews it had.

A half-float texture is drawn through alpha_blend_linear.frag,
cross-compiled from the Vulkan driver's SPIR-V the first time one is
drawn: the inverse of the composite's SDR encode at the menu nits, the
MVP followed by the nits and the composite's gamut. gl3 keeps the names
of the half-float textures it loaded, on the context's own thread, to
tell them apart at draw time.
2026-10-05 11:03:07 +02:00
LibretroAdmin c052dfc7e1 menu: a held direction goes by real time and uses every frame
The menu's auto-repeat was measured in milliseconds but stepped by
frames. Its repeat interval, 33.33 ms, is two frames at 60 Hz; its
count was only looked at once a frame, started again from nothing at
each repeat - dropping what the frame had overshot - and spent the
frame it started on without counting. Acceleration went up by one per
repeat, and fast scrolling moved several entries per repeat. So the
list's speed followed the display: Down held for three seconds moved,
in a frontend-harness lane driving the menu at each rate,

   30 Hz  43 entries
   60 Hz  56 entries
  120 Hz  67 entries   (144 and 240 Hz the same)

and a faster display gained nothing in how finely the list moved: at
any rate, fast scrolling jumped up to six entries at a time.

A held Up or Down now moves the list at a speed in entries a second,
and as often as frames come:

- after Menu Scroll Delay (the user's setting) the list moves 20
  entries a second, and faster as the hold goes on - one step of
  acceleration every 50 ms, by real time, up to six times that with
  fast scrolling (120 a second);
- each frame moves the entries due since the last one and carries the
  fraction. The distance covered is the same at every refresh rate; a
  faster display covers it in more, smaller steps. At 240 Hz fast
  scrolling moves one entry at a time on 266 frames in three seconds,
  where 60 Hz has to jump two at a time on 152;
- the number speed-up of a held Left/Right goes by how long the
  direction has been repeating, as before; Left, Right and the other
  buttons repeat 20 times a second on a schedule that carries over
  what a frame overshoots.

menu_driver_note_action(), last_action_time, prev_action and its
200 ms rule go: menu_event() knows when the direction went down. The
pointer's timings - tap, long press, swipe, its scroll decay - are
human timings already applied by real time, and are left as they are.

The lane, menu repeat, replaces menu press time. It holds Down for
3 s at 30, 60, 120, 144 and 240 Hz: 56 entries at each, the first
repeat at the scroll delay, the same acceleration and speed-up time;
with fast scrolling the same distance at each, one entry at a time at
240 Hz and on at least half again as many frames as at 60 Hz. On
master it fails at every rate but 60 Hz.

Built with overlays and networking off. In a tree configured as the
CI jobs are, all frontend-harness lanes pass, and the menu-harness
samples mouse_click, nav_repeat_stall, playlist_nav, resolution_reset
and network_settings pass.
2026-10-05 10:54:53 +02:00
libretroadmin 0fc65a022a vulkan: the HDR UI layer is FP16, and shows linear scRGB textures
Under HDR the menu - and a shader's SDR output - is drawn into the
offscreen buffer under sdr_render_pass and composited onto the
swapchain. Both are R16G16B16A16_SFLOAT now, where they were
B8G8R8A8_UNORM, so every pipe made for that render pass draws into a
layer whose alpha has every step, that keeps a 10-bit texture's
precision, and that holds linear values above menu white for the
composite. Nothing renders under that render pass outside the HDR path.

An RGBA16F texture, linear scRGB, is drawn into that layer through
display.pipeline_linear_sdr: alpha_blend_linear.frag writes the exact
inverse of the composite's encode, To2020(v^2.4) at the menu nits, with
the composite's own matrices, and takes the menu nits and ExpandGamut
after the MVP in the draw's uniform. While the output is HDR the driver
answers TEXTURE_GPU_FORMAT_SCRGB, so an HDR video previews in HDR here
as on D3D11 and D3D12.
2026-10-05 10:47:15 +02:00
LibretroAdmin 2f6128f050 menu: ten timestamps in the menu's state are five
The menu's state kept ten retro_time_t. Five did a job that needs a
field there; the rest go:

- input_poll_time_us was only the run loop's one-frame-late copy that
  becomes input_time_us next frame: a static in the run loop;
- powerstate_last_time_us and datetime_last_time_us throttled the
  battery and clock refresh, each in one function: statics there. The
  menu's state is never cleared, so nothing changes;
- prev_start_time, noop_start_time and noop_press_time existed only
  to work out action_press_time - how long the same action has gone
  on, which speeds up a held Left/Right on a number - through a
  two-branch state machine in the run loop. menu_driver_note_action()
  does it with one more field, last_action_time: the same action again
  within 200 ms keeps the count going, anything else starts it again;
  a frame with no action changes nothing. A held direction comes as
  one action, the scroll delay (256 ms by default), then one every
  33 ms: the count starts at the first auto-repeat and grows from
  there, as it did. The pause between two of the same action is now
  measured from one to the next instead of across the frames with no
  action between them, a frame or two longer; at a 33 ms repeat that
  changes nothing.

Left in the menu's state: input_time_us, input_last_time_us,
action_start_time, action_press_time and last_action_time.

A frontend-harness lane, menu press time, drives it with a clock of
its own: a direction held for 22 s counts from its first auto-repeat,
never goes down and passes the last speed-up at 21 s; a let-go, a
pause of 300 ms, and another action within 200 ms each start it again.

Built with overlays and networking off. In a tree configured as the
CI jobs are, all frontend-harness lanes pass, and the menu-harness
samples mouse_click, nav_repeat_stall, playlist_nav, resolution_reset
and network_settings pass.
2026-10-05 10:16:18 +02:00
Anthony Schemel 217c50838e net_http: refuse a redirect from https to http
net_http_redirect takes the new scheme from the Location URL, so an
https request redirected to http:// carried on over plain HTTP with
no sign to the caller. A redirect that leaves TLS now ends the
transfer with an error; https to https, relative Locations and
http to https are followed as before.

The request's Authorization and Cookie headers stay with the host and
port they were sent to: a redirect elsewhere goes out without them,
and other headers follow as before. The same host moving from http
onto https keeps them.

This changes behaviour for any caller that relied on such a
redirect: it now fails where it used to succeed unencrypted, and a
cross-host redirect that needed the credentials now gets no further
than the new host's answer to an unauthenticated request.

samples/tasks/http/http_limits_test.c gains lanes for both: the
downgrade and cross-origin cases fail without the change and pass with
it, and the allowed cases pass either way.

Closes #19696
2026-10-05 10:11:49 +02:00
LibretroAdmin 85da87d738 menu: while the pointer is in use, menu_event's own action is nothing again
menu_mouse_click_test has failed since 039bb981c (one hold for what is
held): the mouse wheel no longer scrolled the list, and a press held
for two seconds opened the entry under it as a click would.

When the mouse or the touchscreen was in use, menu_event() set the old
flag that held input back, and at its end dropped its own action for
as long as the flag was set - which, set a few lines up, was this very
frame. The mouse's press, its wheel and the long press are worked on
the pointer path and rely on that: menu_event() acting on the same
frame as a button as well undoes them. The change that replaced the
flag with the hold kept the hold but lost the dropped action.

menu_event() drops its action on a frame the pointer is in use, as it
did; the hold is still taken there, so a button the mouse is bound to
stays held back until let go.

Run with the menu harness samples in a tree configured as the CI job
is: mouse_click, nav_repeat_stall, playlist_nav, resolution_reset and
network_settings pass, and so do the frontend harness lanes.
2026-10-05 10:01:09 +02:00
LibretroAdmin 73dde88f91 replay: a header with no block layout plays without an index
A recorder built without statestream writes block and superblock sizes
of 0 and RAW checkpoints. A statestream build plays such a replay with
no index built for it; a statestream checkpoint in it is refused, as
for a version-1 header. Non-zero sizes are bounded as before.

Lane header_no_layout in samples/tasks/bsv_replay_init.
2026-10-05 09:50:05 +02:00
Anthony Schemel ee8a24a447 replay: harden replay file parsing
Each read is reachable from a damaged .replay file or from replay data
inside a loaded savestate.

- uint32s_index_commit: a commit interval of 1 (from the header) read
  the addition one past the end of the log and freed wild pointers;
  below 2 nothing is collected. The bucket lookup is guarded as pop's.
- reset_playback: block and superblock sizes from the header are
  bounded (REPLAY_MAX_*) and must be a multiple of 4.
- A statestream checkpoint in a replay whose header built no indexes
  is refused instead of read through NULL.
- replay_check_same_timeline: a frame claiming more inputs than a frame
  holds overflowed its 128 KB compare buffers.
- replay_set_serialized_data takes the savestate block's length; the
  replay length it declares must fit inside it.
- push_input_event / push_key_event drop events past their arrays.
- uint32s_index_pop freed nothing it dropped (a leak per rewind).
- movie_find_checkpoint_before returned the frame as a bool: "no
  checkpoint" read as found, frame 0 as not found.
- A checkpoint size of 2^63 or more seeked backwards and hung the scan;
  every file-sized skip now moves forward within the file.
- A checkpoint that fails to load ends playback rather than playing on
  out of step with its indexes, and a failed statestream decode clears
  cur_save_valid.
- Legacy checkpoint frames: a size too large to allocate, or to fit a
  size_t, read into a NULL cur_save; the movie now ends instead.
- bsv_movie_load_checkpoint no longer sets last_save_size to cur_save's
  size. last_save is the recorder's other buffer, swapped back in on
  the next write, so it could claim more than it holds. The reader's
  last_save_size check goes with it; superblock_seq_len already sizes
  the sequence.
- write_checkpoint recorded a size before its buffer existed; a log
  line printed a pointer as %c; a message length used sizeof.

bsv_checkpoint_bounds_test's verbatim copy of bsv_movie_load_checkpoint
follows the two changes to that function.

Tests: samples/tasks/bsv_replay_init gains lanes checkpoint,
statestream, index_gc, index_interval1, index_pop, header_sizes,
no_index, decode_fail_ends (STATESTREAM) and timeline_inputs,
state_replay_len, event_capacity, seek_no_checkpoint, checkpoint_seek.
CI builds it with STATESTREAM=1 too; the ASan runs set
allocator_may_return_null=1, and the test sets it for TSan.

Closes #19695
2026-10-05 09:50:02 +02:00
LibretroAdmin 5925e87cc9 input: the key lane takes no lock, and keeps the releases that do not fit
Key events reported off the frontend's thread - the window procedure
and X11's event loop, which with threaded video run on the video
thread - wait in a queue for the poll. Its writers took turns through
a flag they spun on: a lock in all but name. And when it was full the
newest events were dropped, releases with presses: a dropped release
left the key held in the core until it was pressed again.

The queue is a bounded one of many writers and one reader, with no
lock: each slot has a sequence number saying whose turn it is. A
writer claims the next slot by moving the tail with a compare-and-
swap, fills it and hands it to the reader through the sequence; the
reader hands it back the same way. Writers never wait on each other -
one that loses a slot takes the next - and the reader never waits on a
writer. Each writer's events stay in the order it sent them. A slot's
sequence is kept less its index, so the zeroed queue the program
starts with is its first state.

When it is full a press is dropped and counted, as before. A release
is not lost: it is noted in a bitmap, and the poll, once the queue is
drained, turns it back into the release of a key it last saw go down.
A full queue cannot leave a key held.

The key events lane grows two cases: a release that does not fit
reaches the core and the key does not stay held; and two threads
sending at once each get every event through, in order, on the core's
thread. Its full-queue case is unchanged: sixty-four through in order,
thirty-six counted.

Run in a tree configured as the frontend-overhead CI job is, where
all lanes pass. The queue on its own was run with four writers of
20,000 events each, optimised and under ThreadSanitizer: every event
through in order, no report. Built with overlays and networking off,
and with threads off.
2026-10-05 09:43:22 +02:00
libretroadmin 793d4144e4 menu: a remembered selection lands inside the list it is restored to
menu_entries_restorable_selection() gives a remembered selection for a
list: unchanged while it is inside the list, or while the list is a
placeholder a finished background read replaces ("Loading", Explore
initialising), since it belongs to the full list; otherwise the list's
last entry. RGUI applies it when it fills a list, after restoring its
remembered selections and before recording the selection, and XMB and
Ozone when they restore a tab's selection, so a selection remembered
from a longer list no longer lands past the end, and a tab whose
playlist is still loading keeps its remembered position.

playlist_nav gains two lanes, both through real back presses with
RGUI: a row that now holds a 10-entry playlist after its 1500-entry one
was removed opens on its last entry, and a playlist reopened while it is
read in the background comes back on the entry picked before.
2026-10-05 09:42:44 +02:00
LibretroAdmin 039bb981cb input: one hold for what is held, in the input frontend; the menu's flag is gone
What is held when the menu opens or closes, a bind or a text entry
ends, or the mouse clicks is meant to do nothing more until it is let
go. Two things did that. The core's buttons and keys had a hold in
the input frontend (input_driver_hold_core_input()). The menu and the
hotkeys had input_driver_flushing_input, an unsigned in the menu's
state with values 0, 1 and 2, which the menu set, the run loop counted
through, and the core's input path read to block the core outright.

There is one now, in the input frontend:

- input_driver_hold_held_input() holds back whatever is down, control
  by control, until each is let go - from the core (its buttons, keys,
  mouse and lightgun buttons and the pointer press) and from what the
  run loop and the menu see (the RetroPad bits and the hotkeys).
  Everything else gets through at once. The menu calls it where it
  set the flag; input_driver_hold_clear() where it cleared it;
- the run loop hands its bits to input_driver_hold_bits(), which takes
  out what is held back, and keeps the pause as it was while
  something is - the same pause handling as before;
- the core's input path is no longer blocked by the menu: it reads no
  menu state, and what the menu left held is the hold's to keep back.

input_driver_flushing_input is removed from the menu's state, with
every use of it, and the comments that named it.

The menu pause lane closes the menu with the mouse's left button held
and with the pointer pressed, as it does with B and Return: the core
does not see the click or the touch while it is held, nor after it is
let go, and does see it made again.

Built with overlays and networking off, and in a tree configured as
the frontend-overhead CI job is, where all harness lanes pass.
2026-10-05 09:16:48 +02:00
libretroadmin 796108188d playlist_nav: a playlist past RGUI's slots keeps History's selection
A lane with RGUI, Remember Selection on and 264 playlists: History is
left on its second entry, an entry is picked in the playlist in row 256
- the first row past RGUI's per-playlist slots, where the History
selection follows them - and History must reopen on its second entry.
The fixture history is written where the frontend reads it at startup,
under the playlist directory's builtin/, and is waited for like any
playlist read. The lane fails on the array overrun fixed by 8997645.

The harness gains open_screen(), any displaylist opened the way the
Playlists screen is, and set_selection(), a selection change the menu
driver records.
2026-10-05 09:16:14 +02:00
LibretroAdmin 08e0014b4a samples/png: the decode corpus runs under the sanitizer and links on Windows
The corpus test's objects take SANITIZER= like the rest of the
directory, so its decodes are checked under ASan and UBSan in the
samples workflow; they still build without RPNG_TEST's trace. Its
source list carries rtime.c, which Windows needs for features_cpu.c.
2026-10-05 09:12:45 +02:00
LibretroAdmin e721b63038 rpng_apng_bounds_test: frame rectangles, IHDR and acTL are checked at open
A lane in the png samples for the APNG indexer. It builds animated PNGs
in memory and requires a valid two-frame file to decode with its second
frame where the fcTL puts it, and refuses at open an fcTL whose offset
plus size wraps 32 bits on either axis, a second IHDR and a second acTL.
A second IHDR arriving through set_avail after the canvas is allocated
leaves the canvas and its frames as they were.

A malformed file that opens fails the lane by itself, so it holds with
or without a sanitizer. It builds for Linux and Windows and is run by
the libretro-common samples workflow.
2026-10-05 09:09:33 +02:00
LibretroAdmin 89dd3afaa6 win32: drag and drop from Windows 95 on, elevated windows included
DragAcceptFiles is resolved from shell32, which is always linked, so
drops are switched on whether or not dwmapi exists or HAVE_DYLIB is
set. DWM setup runs once and reports whether DWM is available.

Windows that accept files let WM_DROPFILES, WM_COPYDATA and
WM_COPYGLOBALDATA through the message filter - per window from
Windows 7, per process on Vista - so an elevated RetroArch takes drops
from Explorer. The drop handler resolves DragQueryFileW at runtime and
reads the drop with DragQueryFileA where that is missing or reads
nothing.

samples/gfx/win32_drag_drop runs the setup and the handler from their
shipping text against a fake shell32, user32 and dwmapi for Windows 7,
Vista, XP and 9x.
2026-10-05 09:05:48 +02:00
Acts1631 e269aaaf6c apng: validate frame bounds and control chunk order
fcTL offsets and dimensions are uint32_t. Adding them before
checking against the canvas can wrap and accept a frame outside it.
Compositing or disposing that frame writes outside the canvas.

Check dimensions before subtracting them from the canvas size.
Reject later IHDR or acTL chunks so earlier checks cannot be
invalidated by replacing the canvas or frame table.
2026-10-05 09:08:44 +02:00
LibretroAdmin 9ec5abd8f6 input: the AI service's presses are one word the poll takes whole
The AI service read and wrote input through one int[16] in the input
state. It read: with the service on, the run loop copied sixteen of
user 1's buttons into it every frame, in case a request was sent. And
it wrote: a reply pressed a button by setting its entry to 2, which
the next poll turned into a one-frame override and then cleared -
while the run loop's copy overwrote the same entries every frame.

The two are apart now:

- a press is a bit in ai_press_pending, set with an atomic OR by the
  reply (input_driver_ai_gamepad_press()) and taken whole, with an
  atomic exchange, by the poll that applies it. A reply handled on any
  thread cannot tear a press or lose one, and two presses before a
  poll arrive together;
- the request reads the buttons user 1 holds when it is sent
  (input_driver_ai_gamepad_held(), the RetroPad as bound, which is
  what the copy was), once, instead of the run loop copying them every
  frame.

The run loop's per-frame copy is gone. The presses still go out
through the mapper for user 1 as before.

A frontend-harness lane, AI presses, checks that a press reaches the
core for exactly one frame and that two pressed before a poll arrive
together. It runs where the AI service is built (HAVE_TRANSLATE and
HAVE_ACCESSIBILITY, as in the frontend-overhead CI job) and was run
in a build configured as that job is; elsewhere it says it is
skipped.

Built with overlays and networking off as well.
2026-10-05 08:55:55 +02:00
LibretroAdmin 578cacb9e0 win32: hold the screensaver off with one power request
win32_suspend_screensaver() keeps a single display-required power
request for the process, sets it only when it is not already set and
clears it when the setting is turned off, so repeated video driver
inits share one request and turning the setting off takes effect
immediately. Before Windows 7 it holds a continuous thread execution
state. Every entry point is resolved at runtime, so one binary covers
Windows 95 through current releases.

samples/gfx/win32_screensaver compiles the function from
win32_common.c against a fake kernel32 that counts requests the way
Windows does.
2026-10-05 08:52:59 +02:00
libretroadmin e232d8d4df d3d12: the HDR menu is drawn in FP16, and shows linear scRGB textures
Under HDR the menu, overlays, messages and widgets are drawn into the
back buffer and composited onto the swapchain, and the back buffer was
the swapchain's format: on HDR10 R10G10B10A2, two bits of alpha, so a
menu's fades and translucency over content had four steps. It is
R16G16B16A16_FLOAT now on both HDR outputs, wherever it is made - at
init, on a resize and when the frame finds it otherwise - unless the
shader's last pass renders into it and it keeps that pass's format.

A pipe's RTVFormats must be its target's, so on an HDR10 swapchain the
sprite, font and opaque menu pipes are made a second time for FP16, and
every draw picks its pipe through d3d12_rt_pipe() from the target bound
now; on an FP16 swapchain the HDR set serves both. A menu effect's pipe
is made for the target it is drawn into, again when that changes. An
RGBA16F texture, linear scRGB, is drawn through PSMainLinearHDR, the
inverse of the composite's encode the D3D11 driver uses, and while the
output is HDR the driver answers TEXTURE_GPU_FORMAT_SCRGB, so an HDR
video previews in HDR here too.
2026-10-05 08:51:59 +02:00
LibretroAdmin 5f82ccaf44 menu: the menu's hold is on what was held, not on everything
When the menu opens or closes, a bind or a text entry ends, or the
mouse clicks, input_driver_flushing_input held back all input - every
menu action, every hotkey, and the core's input - until nothing at
all was down. One button still held kept everything else out with
it.

It holds back what was down at that moment, button by button and
hotkey by hotkey, until each is let go; everything else gets through
at once. On the first frame the run loop takes the bits that are down
(1), then masks those out of what the menu and the hotkeys see (2),
and ends the hold when none of them is down. The hotkey-enable button
is never held back, as holding it is how other hotkeys are reached.
The pause handling that ran while the hold was on runs the same.

menu_event() no longer drops every action while the hold is on, and
the run loop no longer skips the remaining hotkeys for it: the held
bits do not reach either.

The menu also starts its OK memory afresh each time it opens. An OK
still down when the menu last closed - by its hotkey, say - stayed
remembered as down; the next time the menu opened it saw OK let go,
and an entry that acts on release, Resume above all, acted on the
menu's first frame and shut it again.

The menu pause lane gains a case: the menu opened with B held. B does
nothing in the menu, and Down pressed while B is still down moves the
selection. On master the menu shuts on its first frame and Down is
dropped.

Built with overlays and networking off as well; all harness lanes
pass.
2026-10-05 08:37:10 +02:00
libretroadmin 8997645db0 menu: remembered selections stay inside their arrays
RGUI remembers a selection per playlist in playlist_selection[], indexed
by the playlist's row in the Playlists screen, and XMB and Ozone one per
tab in tab_selection[], indexed by the tab, which counts one per
playlist. A row or tab past the array is neither remembered nor
restored, as materialui already does with its own slots: with more
playlists than slots, the writes went into the fields after the array -
in RGUI the History and Favorites selections - and the restores read
from them.
2026-10-05 08:36:49 +02:00
LibretroAdmin 6d5d0ed006 win32: low-latency power plan and EcoQoS opt-out
The process opts out of EcoQoS execution-speed throttling at startup
and asks Windows to keep honouring its timer resolution while the
window is minimized or occluded.

Power Management gains a Low-Latency Power Plan setting, off by
default. It activates a copy of the current plan with the processor
performance check interval at 5000 ms and minimum processor state and
unparked cores at 100%, AC values only. The copy records the plan it
came from, so exit - or the next start after a run that ended without
restoring - reactivates the original and deletes the copy. powrprof is
resolved at runtime; without it the setting reports failure and turns
itself off.
2026-10-05 08:30:05 +02:00
LibretroAdmin 8449a58ff2 input: the core hold and the named calls build without overlays
input_driver_hold_core_input() and eight calls added after it -
non-blocking input, the remap cache flag, the device names saved and
restored, the sensor map get and set, and the AI service's buttons -
were defined inside input_driver.c's #ifdef HAVE_OVERLAY block. A
build without overlays (RS90, and the other Dingux targets) failed
to link: menu_driver.c calls input_driver_hold_core_input() as the
menu closes, whatever the build.

They are out of the block now. The overlay calls stay in it, the
eight-way slopes one included, and so do their callers.

Checked with overlays and networking off: input_driver.c, menu_driver.c,
configuration.c, runloop.c and retroarch.c compiled, and every
input_driver_/input_config_ call they need is defined.
2026-10-05 08:24:58 +02:00
LibretroAdmin 2374e872bd GameCube/Wii: the libogc build links against libogc 3 too
With libogc 3 the libogc build links with devkitPPC's own linker
scripts, and its 512 KiB main stack is a buffer __ppc_main_sp points at
rather than the bootstrap/gx scripts' layout. The Wii booter is entered
with interrupts off, net_compat takes libogc 3's addrinfo, inet_ntop,
inet_pton and poll flags, and everything older than libogc 3 builds
exactly as before.
2026-10-05 08:20:29 +02:00
libretroadmin 1455550dd6 gfx_thumbnail: an HDR video's first still is HDR too
A thumbnail's still loads with task_push_image_load_ex() and
TASK_IMAGE_LOAD_HDR, and where the driver offers
GFX_SURFACE_PIXFMT_FP16 the load asks the video for half floats:
rwebm_video and rmp4_video decode a PQ or HLG source's first frame as
linear scRGB straight into the frame they hand out, 8 bytes a pixel,
with no canvas and no copy, and image_transfer_is_fp16() says so. The
thumbnail submits it as FP16, or, where the driver has stopped offering
it since, leaves the picture to the animation that follows; the
resamplers, which know 8-bit and packed 10-bit texels, pass such a still
by. A preview no longer brightens when its animation takes over from
its still.

Only a load that asks gets half floats: task_push_image_load() is
task_push_image_load_ex() without the flag, so RGUI and every other
caller keeps the images it had. video_hdr_fp16_test decodes the PQ
fixtures' stills onto the PQ EOTF when asked, and every other still,
asked or not, as 32-bit words.
2026-10-05 08:12:49 +02:00
LibretroAdmin aafec5221f input: settings, netplay and three tasks ask the frontend by name
Outside the frontend's top level and the replay code, five files
still took the input state for a field or two. Each asks by name now:

- netplay switches non-blocking input for catching up
  (input_driver_set_nonblocking()) and asks whether the core's input
  is blocked (input_driver_libretro_input_blocked(), which was there);
- configuration.c marks the remap cache active
  (input_driver_set_remapping_cache_active()), saves and restores the
  devices' names around a configuration swap
  (input_driver_device_info_save(), _restore()), and resets and fills
  a port's sensor axis map through input_driver_set_sensor_map() -
  the map is built whole and set in one call. The getter,
  input_config_get_sensor_map(), moves to input_driver.c;
- the autoconfig task asks a port's display name
  (input_config_get_device_display_name(), which was there);
- the overlay task asks where the eight-way slopes are kept
  (input_driver_overlay_eightway_slopes());
- the AI service presses and reads its buttons
  (input_driver_ai_gamepad_press(), _pressed()).

What is left outside input/ is the frontend's top level (retroarch.c,
runloop.c) and the replay code being handed the state by the commands,
the save-state and movie tasks and rewind.
tools/input_state_grab_check.py now counts tasks/, network/,
command.c, configuration.c and state_manager.c too, with that list.

The overlay task's sample stubs the slopes call instead of the input
state. The AI service and netplay paths are syntax-checked with their
features on, as the Linux build leaves them out; the harness passes.
2026-10-05 08:06:19 +02:00
Anthony Schemel 9e1c4193be metainfo: add releases 1.20.0 to 1.22.2
The AppStream metadata stops at 1.9.11 (2021), so software centres
show that as the latest release. Add 1.20.0, 1.21.0, 1.22.0, 1.22.1
and 1.22.2. Dates are the tag dates, which match the GitHub releases.
Feature lists are summarised from CHANGES.md. 1.10.x to 1.19.x are
not filled in.
2026-10-05 08:00:05 +02:00
Anthony Schemel c7a261ce54 menu: bounds-check the selection in ozone_selection_changed and xmb's imageviewer update (#19691) 2026-10-05 07:59:18 +02:00
LibretroAdmin 536e8db734 input: a key that closes the menu stays out of the content until it is let go
The core's buttons held as the menu closes are held back until each
is let go (the last change). The keyboard's keys were not: Enter on
Resume reached the core's keyboard on the first frame back.

The hold covers the keys now, the same way: every key is held back as
the menu closes, each poll narrows that to the keys still down - a
word of them skipped at a time once it is clear - and a key still
held back reads as up where the core asks for it.

The menu pause lane grows to match:

- the menu closed with Return held on the keyboard: the core does not
  see it while it is held, nor after it is let go, and does see it
  pressed again. A stand-in input driver has Return down on cue. On
  master this fails with Pause Content in the menu on and off;
- the menu opened with its hotkey and closed with it held a while and
  let go, which is when it acts, while the pause hotkey has the core
  paused: the menu shuts and stays shut, and the core stays paused
  until the pause hotkey again. This passes on master as with this.

Before the B case the lane makes sure the menu is open and staying
open: a menu action left over from the Return case could close it on
its first frame, and the case would then be pressing B in the content.
Pressing B or Return in the content and opening the menu afterwards
was tried on its own, with the setting on and off: the menu stays
open.

The pause handling the run loop does while the menu's own hold is on
is left as it is. It acts only while a hotkey is held across the
menu closing, and the lane's two cases of that pass with it.

All harness lanes pass.
2026-10-05 07:54:15 +02:00
LibretroAdmin 6444aefdda GameCube/Wii: EXTERNAL_LIBOGC=1 selects the libogc build
The core CI templates relink the precompiled HAVE_LIBOGC=1 tree with
EXTERNAL_LIBOGC=1.  That spelling names devkitPro's libogc, so it now
selects HAVE_LIBOGC=1 in Makefile.wii and Makefile.ngc and the relink
gets -logc and the libogc objects the tree was built with.  An explicit
HAVE_LIBOGC still wins.
2026-10-05 07:42:10 +02:00
LibretroAdmin 79c6717b52 runloop: a dummy session describes the selected core
The dummy core stands in for the selected core, so its init reads that
core's system info and no-game support. Static builds boot into a dummy
session with their linked core selected; without this the menu showed
"No core" and offered Load Core instead of Start Core.

The content_load harness starts a dummy session with a core selected and
checks the library name and no-game support come from that core.
2026-10-05 07:32:46 +02:00
libretroadmin 072a70bc75 cloudsync: CI covers the locked hand-over path
Whether completion callbacks hand manifest entries and downloads over
on lock-free lists or under tcs_manifest_lock is decided in one place,
TCS_LOCK_FREE: lock-free where the atomics backend has pointer
operations, unless TASK_CLOUDSYNC_FORCE_LOCK asks for the locked path.

Every backend a CI runner has provides pointer atomics, so the locked
path was never built or run. samples/tasks/cloudsync builds the
manifest-adds and pacing tests with FORCE_LOCK=1 in a new lane of the
tasks workflow, plain, under ASan/UBSan and under TSan, where an append
or hand-over the lock does not cover is a reported race.
2026-10-05 07:31:35 +02:00
LibretroAdmin d74aa4b5a1 input: the button that closes the menu stays out of the content until it is let go
A new frontend-harness lane, menu pause, opens the menu, closes it
with B held, lets B go and presses it again, and pauses with the pause
hotkey across a trip into the menu - with Pause Content When Menu Is
Active on and off - and holds what the core sees and whether it runs
to what they should be.

On master it failed: the core saw the B that closed the menu on the
first frame back. The menu's hold (input_driver_flushing_input) keeps
the core's input blocked only until the run loop's hotkey view has
nothing down, and with the menu shut that view has the hotkeys and
not the core's buttons: it cleared on the first frame. Before the
last change it held one frame longer and then let B through the same
way.

The core's buttons get a hold of their own, the one the menu already
has for its own actions:

- as the menu closes, input_driver_hold_core_input() holds back every
  button of every port;
- each poll narrows that to the buttons the core's ports still have
  down, read as the core sees them, and drops the hold when none are;
- where the core reads its buttons, those still held back read as up.

A button held as the menu closes stays out of the content until it is
let go; pressed again, it gets through, and so does every other button
at once. While nothing is held back it is one test of a flag per read.

The pause checks of the lane pass on master as with this: the menu
pauses the core when set to and not otherwise, and leaving it runs the
core unless the pause hotkey paused it first.

Not covered by this: a keyboard key held as the menu closes (Enter on
Resume) is still kept from the core for the menu's hold only.

All harness lanes pass, the new one included.
2026-10-05 07:30:45 +02:00
LibretroAdmin b29c8f6fcf menu: the input held back after a menu action waits for release, not for extra frames
When the menu opens or closes, a bind or a text entry ends, or the
mouse clicks in the menu, input_driver_flushing_input holds the input
back - from the menu, the hotkeys and the core - so that the press
that did it does nothing more. The run loop counts it down on each
frame with nothing held.

Five of the six places set it to 2, which held input back for one
more frame after everything had been let go. The comment gave the
reason as input being ignored for a frame after some events, such as
the on-screen keyboard closing. They set it to 1 now, as the mouse's
already did: the input is held back until it is all let go, and not a
frame longer.

The wait for release stays; that is what keeps an A pressed on Resume
out of the game.

Run in the Linux build, with each key both tapped and held for a
good part of a second: a search in Settings closed with Enter selects
the match and does not open it; a hotkey bound by pressing Enter is
bound once and does not start a second capture; the menu opened and
closed with F1 held stays as it was put; Resume with Enter held goes
back to the content and the menu stays shut. The frontend harness
passes.
2026-10-05 07:16:15 +02:00
LibretroAdmin 88adacf483 audio/jack: load libjack at run time
With dynamic loading the JACK driver opens libjack itself the first
time it is asked for, through a table of its entry points, so the
binary carries no link dependency on it and starts on systems and
runtimes that do not ship JACK (Steam Linux Runtime 4.0 among them);
there the driver simply fails to initialise and another is used.
Builds without dynamic loading keep linking it. The latency query is
optional, as JACK's own headers declare it.

samples/audio/jack_runtime links the shipping driver against no JACK
library behind a fake loader and builds on Linux and mingw-w64
without JACK installed.
2026-10-05 07:15:45 +02:00
LibretroAdmin e8bd3e2215 cocoa_input: the mouse's last position and its motion are packed too
The last two x/y pairs of the Cocoa input driver go the way of its
positions: the pointer's position at the last poll (mouse_last) and
the mouse's motion (mouse_rel) are one uint32_t each, read and written
with COCOA_POS_PACK(), COCOA_POS_X() and COCOA_POS_Y().

The poll works the motion out from the packed position and the last
one, and keeps the position as the new last one in one store; macOS
adds a moved mouse's delta to the motion the same way. The motion
wraps as the 16-bit fields it replaces did.

No x/y pair is left split in the driver's data.

Compiled as C for the macOS and the iOS branch with the Apple headers
stood in for; the packed motion is tested against the old 16-bit
arithmetic, wrap included.
2026-10-05 07:09:29 +02:00
libretroadmin ce2be126f2 cloudsync: the local files are listed a few entries per run
The current manifest is built by walking the synced directories with
retro_opendir/retro_readdir, one entry per unit of the shared per-frame
I/O window - at least one per run - instead of reading every directory
in one run of the handler, which with Threaded Tasks off is the frame
loop. The walk keeps a stack of open directories in the sync's state
and lists what dir_list_new() listed: files under the root
recursively, hidden ones included, directories left out, a
subdirectory that cannot be opened skipped. A root that cannot be read
still stops the sync.

cloudsync_pacing_test walks two roots of 3000 files with nested,
hidden and ignored entries, compares the result with dir_list_new()'s
and checks the grant per run and the unreadable root.
2026-10-05 07:07:35 +02:00
LibretroAdmin b32d660638 cocoa_input: each position is one packed word
The Cocoa input driver kept every position as two int16_t fields: the
pointer in the window, and four for each touch - where it is on
screen, and the three translations of it (in the viewport, on the
whole screen, held to the viewport's edges). A position was written
one half at a time.

Each is one uint32_t now, x in the high half and y in the low, packed
and read with COCOA_POS_PACK(), COCOA_POS_X() and COCOA_POS_Y(): a
position is written in one store, so its two halves always go
together, and a touch is 16 bytes instead of 16 bytes of eight
separate fields. Both halves stay signed 16-bit values, -0x8000 for
outside included.

The translations are made into locals, as the viewport functions take
an int16_t for each half, and stored a pair at a time. A translation
that fails leaves its pair as it was, as before.

What the driver reports is unchanged. No Objective-C compiler is
available here: the driver is compiled as C for both its macOS and
its iOS branch with the Apple headers stood in for, and the packing
macros are tested on both signs and their limits.
2026-10-05 07:06:22 +02:00
LibretroAdmin 8ac6baa5dc ui: the Apple UI hands its events to the Cocoa input driver; nothing under ui/ takes the input state
The macOS and iOS UI code receives the platform's mouse, pointer and
touch events, and wrote them into the Cocoa input driver's data
itself: it took the driver's data out of the input state, thirteen
times, and set its fields - touch counts, positions, button bits.

The driver takes them through calls of its own now
(input/drivers/cocoa_input.h), and is the only writer of its data:

  cocoa_input_mouse_moved()      a mouse that moved, macOS
  cocoa_input_mouse_moved_by()   a mouse with no position, iOS GCMouse
  cocoa_input_mouse_button()     a button down or up
  cocoa_input_pointer_at()       a pointer hovering, iOS
  cocoa_input_touches_begin(), cocoa_input_touch_add()
                                 the touches on screen, given anew
  cocoa_input_touches_reset()    every touch forgotten, on losing focus
  cocoa_input_mouse_grabbed()    whether the mouse is held

Each does what the UI's code did, and does nothing while the Cocoa
driver is not the one in use. A mouse button past the 32 the driver
keeps bits for is now ignored instead of shifted out of range.

Two more: the iOS keyboard's completion lets go of the line through
input_driver_keyboard_line_end(), and the desktop menu asks
input_driver_mouse_grabbed().

Nothing under menu/ or ui/ takes the input state now, and
tools/input_state_grab_check.py has both lists empty.

No Objective-C compiler is available here: the new driver functions
are compiled as C against the real header; the edited UI files are
checked for balance and for any write to the driver's data left. The
desktop menu's core test passes.
2026-10-05 06:56:01 +02:00
libretroadmin 525b5bf301 rgui: a 10-bit image is narrowed before it is drawn
An image load asks for 10-bit output wherever the driver samples it,
and RGUI draws thumbnails and wallpapers in software from 8-bit ARGB
words, so on such a driver a 16-bit PNG reached it as packed
XRGB2101010 and was drawn with the wrong colours. rgui_load_image()
narrows a 10-bit image to the 8-bit words in the load's channel order
before either is processed, the pixels an 8-bit decode would have
given it.
2026-10-05 06:49:38 +02:00
LibretroAdmin 89aa95bfda input: the frontend knows what a line of text is for, and brings up the platform keyboards
Each platform with a keyboard of its own found out about text entry
its own way. iOS and Android had their keyboards started by the menu,
in both dialog openers, with pointers into the frontend's line. SDL3
and the Vita asked the menu on every poll whether a dialog wanted the
keyboard, and the menu for its label and text type.

The frontend keeps what the line is for now. The menu opens a line
with a label and a type (input_driver_text_entry_open()), and:

- iOS, and Android when its system keyboard is chosen, are brought up
  there, by the frontend, which owns the line they edit in place. The
  menu's two copies of that code, and the call that handed the menu
  pointers into the line for it, are gone;
- SDL3 and the Vita look at whether a line is open
  (input_driver_keyboard_line_enabled()) and ask the frontend for the
  label and type (input_driver_text_entry_label(), _type()). Neither
  calls the menu, and SDL3 needs no HAVE_MENU for it.

The text types are the frontend's own (enum input_text_type), in the
menu's order, which a compile-time check in menu_driver.c holds.

Run in the Linux build: Search typed into, a hotkey bound. The Android
single-file build compiles, which takes the Android branch; the iOS
branch is syntax-checked; the SDL3 driver is syntax-checked; the Vita
driver is not built here.
2026-10-05 06:45:53 +02:00
libretroadmin 2c58282e20 gfx_thumbnail: an HDR video previews in HDR where the output is
An animated thumbnail whose source is HDR - PQ or HLG, from its WebM
Colour element or MP4 colr box - is a surface of half floats where the
driver offers GFX_SURFACE_PIXFMT_FP16, which it does only while it
shows such a texture as linear scRGB on HDR output. Its stream is asked
for them: rwebm_video and rmp4_video blit a 10-bit PQ or HLG frame, VP9,
H.264 or H.265, through rwebm_video_blit_i420_fp16 into the caller's
frame with no tone map, and only into the caller's, so a stream's own
canvas never grows; image_transfer asks and answers for both. A frame
of such a surface comes only from a decode into its slot as half
floats; one that came out any other way ends the animation on the last
good frame. Every other source, and every output that is not HDR,
takes the paths it always did.

samples/formats/video_hdr_fp16 decodes HEVC Main10 and VP9 profile 2
fixtures tagged PQ onto the PQ EOTF of their grey, and holds their
SDR-tagged twins and any stream's own canvas to 32 bits;
gfx_thumbnail_preview runs the PQ fixture through the thumbnail with a
driver on HDR output, on the poll and on the worker, and every other
run to not one half-float frame.
2026-10-05 06:42:37 +02:00
libretroadmin adcce20ccb menu: Cloud Sync on SMB or NFS shows the share settings it uses
With the SMB or NFS Cloud Sync driver selected, the Cloud Sync screen
lists the SMB or NFS Network Settings entry, the same one Settings >
Network has, so the server, share and credentials the driver uses are
reachable from where it is chosen. Only the selected driver's entry is
shown, as with the WebDAV and S3 fields; no new strings.

menu_network_settings_test checks the entry under each driver and that
it runs the same action as the Network one.
2026-10-05 06:40:15 +02:00
LibretroAdmin 97f360e6d4 menu: nothing under menu/ takes the input state
menu_driver.c took the input state in nine functions, for text entry,
the capture of a bind, the overlay and a few flags. Each is a named
call on the frontend now:

- text entry: input_driver_text_entry_open() lets go of any line that
  was open, opens one for the callback and keeps the keys from the
  binds - three steps each dialog did by hand. The platform keyboards
  (Android, iOS), which edit the line in place, get where its length
  and cursor are kept (input_driver_keyboard_line_fields()). The
  on-screen keyboard's presses, the touch keyboard's characters, the
  line's length, emptying it, and the text box's focus have a call
  each;
- the capture of a bind: keys kept from the binds, the wait for every
  key to be let go, and the callback each key goes to;
- the overlay: up, up with a page, up with a page that takes input;
- the core's input blocked, the hotkeys blocked, game focus and
  whether the core asked for it.

Each call does what the line it replaces did.

With this, nothing under menu/ takes the input state, and
tools/input_state_grab_check.py has menu_driver.c off its list. What
is left there is the Apple UI (13) and the desktop menu (1).

Run in the Linux build: Search opened, typed into and closed with
Escape; text typed, the focus moved to the text box and a character
removed; a hotkey bound from a key. The frontend harness passes; the
Android single-file build compiles, which takes the platform-keyboard
branch. The iOS branch is syntax-checked only.
2026-10-05 06:33:03 +02:00
LibretroAdmin 6d80539403 menu: the menu drivers ask for the line of text; they take no input state
The four menu drivers took the input state to draw text entry: the
line being typed and where its cursor is, and whether the text box or
the on-screen keyboard has the focus.

They ask for both by name:

- input_driver_keyboard_line_view() gives the line's bytes and where
  the cursor stands in them, already kept inside the line - each
  driver did that clamp itself;
- input_driver_keyboard_textbox_focus().

Five smaller uses in the menu's other files go the same way:
- the entry that renames from a playlist label puts it in the line
  with input_driver_keyboard_line_append();
- the lists that asked the input driver its name through the driver
  pointer use input_driver_get_ident(), which was there;
- the haptics list asks input_driver_has_keypress_vibrate();
- the Android keyboard list takes the driver's data from
  input_driver_current_data().

No menu driver takes the input state now, nor does anything under
menu/ but menu_driver.c, for text entry, the overlay and a few flags.

tools/input_state_grab_check.py holds it: the menu and the UI folders
are counted, with what is left listed file by file (23 uses in 5
files), and a count that only goes down.

Run in the Linux build: Search opened, three letters typed and the
focus moved to the text box, in RGUI, Ozone, XMB and Material UI. The
frontend harness passes; the Android single-file build compiles.
2026-10-05 06:22:42 +02:00
LibretroAdmin ce469a25bf win32: the window has its IME only while a line of text is open
A window has an input context by default, and while it has one every
key that comes off its queue is first offered to the IME - on Windows
8 and later the text services framework, for every keyboard layout.
RetroArch uses what the IME produces in one place, a line of text
typed in the menu. Everywhere else the offer is work on the thread
that pumps the window, on each key down, up and repeat, and an IME
left switched on puts its composition in front of a game.

The main window is now without its input context except while a line
of text is open:

- the frontend says when a line opens and closes, from the two
  functions that do it, through one call, input_text_entry_changed().
  That is the one place the platform is told. The other platforms
  still find out their own ways and are to be told from there too;
- win32_text_entry() keeps what was last said and posts a message to
  the window. Nothing waits for it;
- the window procedure applies it on the thread that owns the window,
  and a window that is made while a line is open keeps its context.

ImmAssociateContextEx() is looked up when first wanted, as Windows NT
4 and 95 do not have it; without it nothing changes. Text from a
layout that needs no IME comes from TranslateMessage() and is not
touched. The desktop menu and the file dialogs are other windows with
input contexts of their own.

No setting: nothing RetroArch does uses the IME outside a line of
text.

Run in the Windows build under Wine, with and without threaded video:
the window's input context is gone once the window is made, back when
Search opens, gone again when it closes, each time on the window's own
thread; typing in Search works. What it saves on a key press on
Windows itself has not been measured, and whether the touch keyboard
and the emoji panel behave the same with it is not known.
2026-10-05 06:05:52 +02:00
libretroadmin 80969bca84 menu: say why Android shows no EDID
Display Information > EDID on Android explains that the system does not
let apps read the EDID of a phone or tablet's built-in screen, and that
a TV box driving a display over HDMI exposes it where the device
allows. The explanation every other platform gives - a laptop or
all-in-one panel carries none, an external display over DDC does -
describes hardware an Android device does not have.
2026-10-05 05:59:00 +02:00
LibretroAdmin a2debaa3fa menu: the on-screen keyboard's grid, cursor and page are the menu's
The on-screen keyboard is drawn by the menu drivers and driven by
menu_event(), and its state was kept in the input state: what its
keys show (osk_grid), where its cursor is (osk_ptr) and which page of
keys is up (osk_idx). Nothing in input/ read them through the input
state; the menu took the input state to reach its own widget.

The three are fields of the menu's state now. The four menu drivers
and menu_driver.c read them there, and the frontend sets the starting
page there.

Still in the input state, because the frontend's handling of keys
uses them: whether the text box has the focus, and the last character
typed, which the Korean pages compose with. They belong with the line
of text, and go when text entry does.

Run in the Linux build: the keyboard comes up on its lower-case page,
the cursor moves with the arrows, and a click on a key types it.
The page keys were not exercised: on RGUI a click where the shift key
is drawn types the key to its left, on master as with this.
2026-10-05 05:49:56 +02:00
libretroadmin 328f65a195 cloudsync: downloads are hashed by the task, a chunk at a time
A finished download is handed to the task thread instead of being
hashed whole in the fetch callback, which runs on the main thread for
WebDAV, S3 and Google Drive and on the handler's thread for SMB and
NFS. The task hashes queued downloads 256 KiB at a time inside the
same per-frame I/O window as the diff - one window per run for both -
and only then records the fetch and drops it from the in-flight count.
The hand-over is lock-free where pointer atomics exist, under the
manifest lock otherwise.

The path buffers of the directory walk, the local-file backup and the
local manifest commit are on the heap, one block per call, keeping
every frame in task_cloudsync.c under 4 KiB. cloudsync_pacing_test
covers a 9 MiB download reported from another thread.
2026-10-05 05:46:10 +02:00
libretroadmin 1b6fca6f4b gfx: the deferred shader LUT loads clear the image they ask for
The deferred shader-load paths of d3d10, d3d11 and d3d12, and gx2's LUT
loader, clear their stack struct texture_image before
image_texture_load() as their non-deferred twins do, so its pix10 - the
request for a 10-bit decode the load reads - is no longer whatever the
stack held, and a 16-bit PNG LUT comes back as the 8-bit texels these
paths upload rather than, now and then, packed 10-bit words.

tools/texture_image_init_check.py, run in the samples-gfx workflow
with a self-test, holds every local or static image that reaches an
image_texture_load* call to having its request set first: by memset, a
zero initialiser or its own pix10.
2026-10-05 05:23:13 +02:00
libretroadmin 2a6021abe3 image_decode_slice_test: a loaded image's format fields are clear
The image a load task delivers says it is 8-bit in every format field
- fp16 and pix10 both clear - checked byte for byte, since a bool
holding ASan's 0xbe malloc fill can test false. An image delivered
with one left unset, which a driver sizes as half floats, fails the
test where it used to load a texture twice the image's size.
2026-10-05 05:23:10 +02:00
LibretroAdmin 88f238d21d menu: the capture of a bind asks the frontend for the controller
The capture of a bind read the joypad drivers itself: it took them
out of the input state, polled them, and read every button, axis and
hat of the controller, once as the capture started (where the axes
rest) and then every frame.

It asks the frontend, input_driver_capture_pad(), which does the same
reads in the same order and fills the menu's arrays. The menu holds no
joypad driver for it any more.

Two other places outside input/ called a joypad driver, and each has
a call that says what it wants:

- a driver restart polled the joypad and input drivers by hand, so as
  not to act on what was read before it: input_driver_poll_devices();
- saving a controller's profile from the menu looked the profile up
  again with the driver's name for the pad:
  input_driver_autoconfigure_pad().

With these, nothing outside input/ calls an input driver or a joypad
driver. tools/input_state_grab_check.py counts both and fails on
either.

Run in the Linux build with the scripted test controller: a bind
captured from a button (Button 20) and from an axis (Axis +2), and
from a key and a mouse button as before. The frontend harness passes.

menu/menu_setting.c fails the syntax gate before this change as after
it, on an include path the gate does not have.
2026-10-05 05:21:38 +02:00
libretroadmin b77f298622 cloudsync: the diff is paced by the I/O window; a sync releases its state
The diff runs as many steps per handler run as the shared per-frame I/O
window grants, at least one chunk of work each run, with no more than
four transfers in flight. The current file is hashed 256 KiB at a time
between steps and carried over to the next run when the window closes,
so a large save, memory card or system file no longer holds one run of
the handler - the frame loop with Threaded Tasks off - and small files
go several to a run instead of one per frame.

The state is released by the task's cleanup when it retires; every
manifest owns its entries' hashes, an entry added to the updated
manifests taking a copy. samples/tasks/cloudsync/cloudsync_pacing_test
checks the pacing against a stub window and runs a sync to retirement
on the real task queue under LeakSanitizer.
2026-10-05 05:13:42 +02:00
libretroadmin e892326fc9 texture_image: fp16 is set wherever an image is filled in
The image a load task hands its caller is malloc'd and filled field by
field, and fp16 was not among the fields, so it held whatever the heap
did: where that was non-zero a driver took 32-bit pixels for half
floats, made the texture at eight bytes a pixel and read twice the
image out of its buffer - Vulkan's initial copy faulted reading past
it on the video thread. The task now carries fp16 through from its own
image, which starts false; image_texture_load() answers it as false
with pix10, on success and on failure, since no decoder emits half
floats; and the textures gfx_display builds by hand set it.
2026-10-05 05:10:59 +02:00
LibretroAdmin 301f30c3df menu: the menu asks the frontend for the mouse, the pointer and the keys
The menu was the one place outside input/ that called an input
driver's state function itself, fourteen times: for the mouse and its
buttons and wheel, for the touch pointer, and for the mouse buttons
and keys while a bind is being captured. Each call took the driver,
its data and the joypads out of the input state and handed them back
to the driver.

It asks the frontend now, and holds none of those:

- input_driver_device_state() for a mouse, a pointer or a key, with no
  binds, as the menu's mouse and touch reads were made;
- input_driver_bind_capture_state() for the capture of a bind, with
  the frontend's binds and the port's pad, as that read was made;
- input_driver_has_device_state() for whether there is a driver to
  read at all, which the menu tested on the driver itself.

Each read is the one it replaces. The three menu functions lose the
five parameters that only carried the driver to the call, and
menu_event() the four locals it filled for them every frame.

tools/input_state_grab_check.py counts this too: a file outside input/
that calls an input driver's state function fails.

Still the menu's own: the capture of a bind polls the joypad drivers
and reads their buttons, axes and hats itself. That goes behind the
frontend's listen call, with Find a Button by Pressing It.

Run in the Linux build: a click on a menu entry enters it; a bind is
captured from a key (Key j) and from a mouse button (Mouse 1). The
frontend harness passes.
2026-10-05 05:08:16 +02:00
libretroadmin 716f6a521c d3d11: the HDR menu shows linear scRGB textures, and its UI layer has one format
Under HDR the menu is drawn into the back buffer and composited onto the
swapchain at menu nits, a composite that shows To2020(v^2.4) * nits for
every texel v, scRGB and HDR10 alike. An RGBA16F texture is linear scRGB
and is drawn through PSMainLinearHDR, which writes the exact inverse of
that, so the composite shows it at the luminance it was graded at;
straight into the swapchain it is scRGB as it is or absolute PQ. While
the output is HDR the driver answers TEXTURE_GPU_FORMAT_SCRGB, and
gfx_surface offers FP16 to producers.

The back buffer's format is d3d11_back_buffer_format() wherever it is
made: the last shader pass's, else R8G8B8A8_UNORM, where a resize used
to make it the swapchain's and gave an HDR10 UI two bits of alpha. Once
the menu draws a texture wider than eight bits it is R16G16B16A16_FLOAT,
so 10-bit thumbnails keep their precision and linear values above menu
white are held for the composite; it is compared with what was asked
for, not with the closest format the device gave.
2026-10-05 05:07:41 +02:00
libretroadmin 5e968f8b99 cloudsync: blocking drivers run on a worker, results come back by poll
SMB and NFS are flagged CLOUD_SYNC_DRIVER_FLG_BLOCKING: their calls
block on the network until done. With threads, cloud_sync_*() queues
them for one worker per sync, which runs them in call order, and
returns at once; cloud_sync_poll(), called first by the task handler,
runs each completion handler on the handler's own thread. A slow or
unreachable server no longer holds up the thread driving the sync -
the frame loop itself with Threaded Tasks off. Each call is one
allocation with its names copied; the worker leaves once the sync ends
or fails to begin. Without threads the calls are made directly.

On exit, after the task drain, cloud_sync_deinit() drops the calls not
started (closing an upload's file) and waits at most a second for the
one under way, so no driver code runs into the teardown.
samples/tasks/cloudsync/cloudsync_async_test covers ordering, threads,
copying, refusals, worker turnover and the exit path in CI.
2026-10-05 04:38:55 +02:00
libretroadmin b2d30b734a gfx: half-float textures load and update on Vulkan, GL3, D3D11 and D3D12
A texture_image can carry RGBA half floats, flagged by fp16, and the
drivers whose texture path keeps them as floats answer
TEXTURE_GPU_FORMAT_RGBA16F: Vulkan loads them as R16G16B16A16_SFLOAT
and streams them through its stream state at eight bytes a pixel, lent
memory included; gl3 stores RGBA16F from GL_HALF_FLOAT with no swizzle,
since they are already in R,G,B,A order; D3D11 and D3D12 keep
R16G16B16A16_FLOAT, which dxgi_copy moves row for row. A gfx_surface
submit in GFX_SURFACE_PIXFMT_FP16 reaches such a driver as half floats
and fails on any other, as does a texture load of them, there being no
narrower form to fall back to.

The surface requirements still do not offer FP16 to producers: every
menu composite treats its textures as SDR-encoded, and a linear texel
would be encoded twice. threaded_video_test loads, updates and reloads
an FP16 still on a real driver that keeps half floats, and streams FP16
frames through the same texture.
2026-10-05 04:38:35 +02:00
LibretroAdmin 6da6466e88 input: no file under input/ takes a state pointer but the frontend
The last 23, in 11 files, are gone. Nothing under input/ calls
config_get_ptr(), input_state_get_ptr(), runloop_state_get_ptr(),
menu_state_get_ptr() or video_driver_get_ptr() any more, apart from
the frontend itself (input_driver.c, input_driver_choice.c).

The replay code (input/bsv/bsvmovie.c), seven:
- its four entry points that took no state are handed the input state
  by their callers, the save-state task and the rewind;
- its two inner functions used the state only to say the replay is
  over, fifteen times between them. That is bsv_movie_set_end() now,
  and they are handed the replay alone.

The platform drivers, fourteen settings reads: named calls, as the
others became - Apple (keyboard as gamepad, mouse index, rumble gain,
device vibration), Wii and GameCube (mouse index and scale, joypad
index), Vita (back touch), Switch (split Joy-Con), webOS
(screensaver), udev (the five touch options).

Two drivers wrote to the settings, and each has a call that says what
it is doing:
- the Wii and GameCube joypad driver puts port 1 back on the first pad
  when only one is left: input_config_set_joypad_index();
- the udev input driver, finding no device on the very first start,
  makes linuxraw the configured driver:
  input_driver_first_start_fallback().

The SDL2 input driver took the video driver's data to grab the pointer
in its window. The video driver does the grab, sdl2_video_grab_window(),
and the input driver asks.

The Android input driver held the code that writes SRAM, core options
and the configuration when Android takes the application away. That is
frontend work; it is retroarch_save_for_suspend() in retroarch.c, as
it was, and the driver calls it.

tools/input_state_grab_check.py has an empty list, and no exemption
for the replay code: a file under input/ that takes one of these
fails.

Built for Linux and Android; the frontend harness, the four replay
tests and the udev devices test pass. The Apple, Wii, GameCube, Vita,
Switch and webOS files are not built here.
2026-10-05 04:31:56 +02:00
LibretroAdmin 8a347234a4 D3D: shaders compile with the newest D3DCompiler that loads
D3DCompile tries each D3DCompiler_*.dll in turn, newest first, as
D3DCompileFromFile and D3DReflect do, so a system without
D3DCompiler_47.dll compiles with the next one it has.

samples/gfx/d3dcompiler_fallback runs the loader under Wine with the
newest compiler disabled, and with all of them.
2026-10-05 04:06:52 +02:00
LibretroAdmin 6cc00b4f40 D3D12: CPU-written buffers go to the GPU upload heap where the device has one
Where the device maps all of its video memory (resizable BAR) and the
runtime offers the GPU upload heap, the constant, vertex and mesh
buffers the CPU writes live in video memory, and the GPU reads them
there rather than across the bus. Elsewhere, and for a buffer the GPU
upload heap refuses, they stay in the upload heap. The font's sprite
buffer, which the CPU reads back, stays in the upload heap always.

samples/gfx/d3d12_upload_heap checks the choice and the fallback, and
a round trip through such a buffer on the D3D12 device there is.
2026-10-05 04:06:48 +02:00
LibretroAdmin 2a9892606a input: the replay code and the Android drivers ask by name
The replay code took the run loop's state for two things, and the
settings twice a frame:

- whether the run loop is paused is runloop_get_flags();
- whether the keyboard callback in place is the frontend's own, which
  is where a replay sends its recorded keys, is
  runloop_key_event_is_frontend();
- the checkpoint interval and whether a checkpoint is loaded are
  handed to bsv_movie_next_frame() by the run loop, which has the
  settings in hand. With a replay on, that is two calls for the
  settings a frame that are no longer made.

It still takes the input state, and stays that way: its state is kept
there, so it is the frontend's own code reaching its own data. The
check says so (OWN_STATE): that file may take the input state and
nothing else.

The Android input and joypad drivers read nine settings; each is a
named call now, the Android-only ones under ANDROID - sensors, the
stylus's four, the disconnect workaround, the physical keyboard, the
block timeout, device vibration.

One read is left in the Android input driver: it saves the
configuration when Android takes the application away. That is
frontend work sitting in an input driver, and belongs out of it.

Left in tools/input_state_grab_check.py: 16 in 10 files - that one,
Apple, the consoles, udev's touch options, webOS, and the SDL2 input
driver taking the video driver's window.

Built for Linux and Android; the four replay tests and the frontend
harness pass. input/bsv/bsvmovie.c fails the syntax gate before this
change as after it, on a type the gate's C89 lane does not define.
2026-10-05 04:01:41 +02:00
LibretroAdmin 774fd01b65 input: drivers ask for a setting by name and do not take the settings
A driver that needed a setting took all of them and picked the field
out:

  if (config_get_ptr()->uints.input_mouse_index[port] == 0)
  wr->nowinkey = config_get_ptr()->bools.input_nowinkey_enable;

Thirteen named calls on the frontend answer instead, each for one
setting or one question:

  if (input_config_get_mouse_index(port) == 0)
  wr->nowinkey = input_config_get_nowinkey_enable();

They join the input_config_get_...() calls that were there. What the
drivers depend on is that list; the layout of the settings is the
frontend's business. Each call sits under the condition its setting
has, and answers with the default before the settings are loaded,
which is what the drivers' own checks for that did.

Twenty-eight reads are converted, in thirteen files: the mouse index
in the SDL 1.2, SDL2, SDL3, DirectInput and X11 input drivers; the
Windows key, background keyboard, overlay and joypad driver in the raw
input and DirectInput drivers; XInput and Player Lights in the raw
input joypad driver; the rumble gain in the udev, SDL3 and Dingux
joypad drivers; the profile directory and system keyboard for SDL3;
the keyboard layout for xkb; the test drivers' scripts.

The samples that link a driver without the frontend get the calls from
one shared header, samples/input/input_config_stubs.h, which reads
their own settings.

Left, in tools/input_state_grab_check.py: 35 in 12 files - Android,
Apple, the consoles, udev's touch options, the replay code, and the
SDL2 input driver taking the video driver's window.

Built for Linux, Windows and Android; the bridge, udev, X11 and raw
input tests and the frontend harness pass.
2026-10-05 03:43:44 +02:00
libretroadmin 9274caf034 cloudsync/nfs: never lose a save to a failed transfer
A download is written to <file>.rafetching and renamed over the local
file only once complete, so a failed open or a read failing part way
leaves the save as it was; the task skips leftover .rafetching files.
Only the server answering NOENT counts as "not there": an unanswered
open, stat or unlink fails rather than reading as a missing file or a
finished delete. An upload is one RENAME over the old file; with
destructive sync off the old file is first kept as <path>-<time>, then
-2 to -9 within one second, and a backup that cannot be made or a stat
with no answer fails the upload instead of replacing unguarded.

Names that do not fit are refused rather than truncated, empty
components in nfs_subdir are dropped, and a directory another client
makes at the same moment is accepted. Path buffers and the transfer
buffer sit in one scratch block allocated per session, keeping every
frame small. samples/network/nfs_backup runs the driver against an
in-memory export in the samples CI workflow.
2026-10-05 03:39:20 +02:00
libretroadmin b3e64ef264 net_nfs3: a call the server never answered has no status
rnfs_get_status() reports the server's answer to the last request a call
sent. Each request clears it before going out, as do a walk, a connect
and a failed redial, so a call refused locally (an over-long name, no
connection, a timeout) reads RNFS_STATUS_NONE instead of the answer to
an earlier call. RNFS_STATUS_NOENT names NFS3ERR_NOENT/NFS4ERR_NOENT for
callers that tell "not there" apart from "no answer".

nfs_test checks NOENT for a missing name through stat and open, a
refused path and a dropped connection with a failed redial each leaving
RNFS_STATUS_NONE, recovery once the server is reachable again, and
RENAME replacing an existing name, under unfs3 (v3) and nfs-ganesha (v4).
2026-10-05 03:39:20 +02:00
Roman Vasilyev a7c4f1e7c1 cloudsync: add NFS backend
Use the built-in NFS client for cloud synchronization, including atomic uploads and non-destructive backups. Enable the backend in regular and Griffin builds so it is available on webOS, iOS, and Android.
2026-10-05 03:39:20 +02:00
LibretroAdmin 2bd0428eea input: drivers ask the frontend, the menu and the run loop by name
An input driver that needed to know whether the menu was up, or had to
say that the platform's keyboard was showing, took the whole state it
lived in and picked the field out:

  if (menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE)
  input_state_get_ptr()->flags |= INP_FLAG_NATIVE_KB_SHOWN;
  runloop_state_get_ptr()->flags |= RUNLOOP_FLAG_PAUSED | RUNLOOP_FLAG_IDLE;

That ties every driver to the layout of four structures and leaves
nothing to say what a driver may read or change.

Forty of these are replaced, in eleven files, with a named call on the
side that owns the state:

- the input frontend: the platform's keyboard is available, is shown;
  a line of text is being typed, is handed over, is done; pointer
  input is blocked; game focus; the mouse grab; the input driver's
  data for a joypad driver that is part of it
- the menu: menu_driver_alive(), and the label of the open text entry
- the run loop: paused by the platform, content loaded (and
  runloop_get_flags(), which was there)

Each call does what the line it replaces did. No driver under input/
takes the input, menu or run loop state any more, but for the replay
code in input/bsv.

What is left is listed in tools/input_state_grab_check.py, file by
file: 63 in 24 files, of which 52 are a driver reading the settings,
10 the replay code, and one the SDL2 input driver taking the video
driver's window. The check runs in CI; a count can go down and not up.

Built for Linux, Windows (four raw input tests under Wine) and
Android, with the frontend harness. The PSP, PS3, QNX, web and webOS
files are not built here.
2026-10-05 03:31:35 +02:00
LibretroAdmin da43ab9733 sdl3: a PS3 controller's pressure-sensitive buttons are bound
ed489ffbf made the SDL drivers read how hard a PS3 controller's
buttons are pressed, on ten axes after the gamepad's six, when SDL's
own driver for the pad is on (SDL_JOYSTICK_HIDAPI_PS3=1). Nothing was
bound to those axes, and binding a button by pressing it binds the
button, so no core saw any of it.

The SDL3 driver's pads take their bindings from one built-in profile.
A PS3 controller now gets a second one: the same, and each of the ten
buttons bound to its pressure axis as well - the button says pressed,
the axis says how hard. It is chosen for that controller under the
SDL3 driver only; every other pad keeps the profile it had, with
nothing more to read.

Without SDL's driver the pad has six axes and the new bindings read
nothing.

The profile's text is checked for the ten bindings beside the buttons.
Not run with a PS3 controller.
2026-10-05 03:12:05 +02:00
Thomas Mannay ed489ffbf6 input: Enable PS3 analog buttons 2026-10-05 03:06:34 +02:00
pstef 6e1f1858fa udev: fix stick axes misdetected as triggers after reconnect
A Bluetooth pad that reconnects is opened before its first input
report, so every axis reads at its minimum and centred Z/RZ stick axes
(e.g. the C-buttons of the 8BitDo 64) are flagged as triggers. Clear
the flag when the axis's first report is near centre.

The flag was also stored by evdev code but read by axis index, so on
pads whose axes are not laid out X, Y, Z, RX, RY, RZ it applied to the
wrong axis. Store and read it by axis index.

Vibecoded.
2026-10-05 03:06:32 +02:00
LibretroAdmin 326c19bb44 tools: a check that a setting's label exists wherever its translations name it
The build break fixed in 418c99cf9 came from three setting rows whose
guard mentioned a SETTINGS_DEF pass and still depended on a feature:

  #if defined(HAVE_X) && !defined(SETTINGS_DEF_CONFIG_PASS)

intl/json2h.py drops a guard line that mentions a pass, so the
translations named the labels in every build while the labels only
existed with the feature. Nothing failed until Crowdin delivered the
first translation of those strings, days after the rows were written.

A row written that way again would break the same way, at the next
fetch after someone translates it. tools/settings_def_guard_check.py
stops it when the row is written: for every guard line that mentions a
pass, in a definitions file msg_hash.h takes its labels from, the line
has to come out the same whatever the feature macros are, with the
passes set as the label list and the strings set them, within the
builds that compile the row's region at all.

It passes on master, 587 guards. On the tree before 418c99cf9 it
names the three rows and nothing else. Its self-test holds both the
wrong forms and the right ones. It runs in CI beside the other source
checks.
2026-10-05 03:06:24 +02:00
LibretroAdmin 418c99cf92 settings: three labels exist wherever their translations name them
Builds without the MCP server stopped compiling, and so did builds
with the NFS client but not the SMB one:

  intl/msg_hash_sk.h: error: 'MENU_ENUM_LABEL_VALUE_MCP_SERVER_TOKEN'
  undeclared
  intl/msg_hash_sk.h: error: 'MENU_ENUM_LABEL_VALUE_SMB_CLIENT_KDC'
  undeclared (and SMB_CLIENT_REALM)

That is Android, iOS, DOS, the MSVC ARM64 build and the Linux builds
configured without networking.

A translation file names a label for every string it has. Where a
setting belongs to a feature, intl/json2h.py copies the row's guard
into the translation file - unless the guard line mentions a
SETTINGS_DEF pass, in which case it drops the whole line, on the
understanding that such a line is always true for strings. These
three rows had

  #if defined(HAVE_X) && !defined(SETTINGS_DEF_CONFIG_PASS)

which mentions a pass and is not always true: it needs the feature.
So their translations came out unguarded, naming labels that only
exist with the feature. Nothing failed while no translation had them;
the Crowdin fetch in 553db673e brought the first, and every build
without the feature broke from there.

The rows now make the label wherever the translation is compiled:

- the MCP server's token, in every build, as the server's other two
  rows already do;
- the SMB client's Kerberos realm and KDC, with either network
  client, which is the guard their region of msg_hash.h and their
  translations have.

The settings themselves still need their feature, and stay out of the
configuration table as before. Builds with the features on are
unchanged, and the next Crowdin fetch produces the same files.

Compiled for the seven combinations of the three features: five fail
on master, none with this. The Android single-file build goes from
twelve errors to none, and the Linux build without the MCP server
builds.
2026-10-05 03:00:34 +02:00
LibretroAdmin b208c36f87 Vulkan: streamed frame textures go to video memory where the device maps all of it
With resizable BAR, a software frame is written straight into the
texture the GPU samples, with no staging buffer and no copy. A texture
lent to the core stays in cached system memory, since the core and the
paused redraw read a lent frame back. A refused allocation falls back
to the staging path.

samples/gfx/threaded_video: check-vulkan-rebar runs check-vulkan
through a layer that reports a resizable-BAR memory layout, and checks
where frame textures and lent frames land, with and without that
memory refused.
2026-10-05 02:54:00 +02:00
libretroadmin e025e290c1 image_hdr_blit: HDR video to linear scRGB, and HLG taken as HDR
rwebm_video_blit_i420_fp16() turns a 10-bit PQ or HLG source into
linear scRGB in RGBA half floats, for a display that shows HDR: PQ by
its EOTF to absolute nits, HLG by its inverse OETF and the BT.2100 OOTF
for a 1000-nit display on the pixel's luminance, both over 80 nits and
from BT.2020 to the 709 primaries, with no tone map and no clamp, so a
colour outside 709 keeps the negative components scRGB carries it in.
The transfers come from static tables computed from the standards'
constants, so concurrent decodes share nothing they write. Any other
transfer is not HDR and is refused.

The SDR paths tone-map HLG as they do PQ, through its own table, where
an HLG stream was converted as if it were SDR; PQ and SDR sources come
out byte for byte as before. libretro-common/samples/formats/
image_hdr_blit checks both against double-precision references.
2026-10-05 02:53:20 +02:00
LibretroAdmin eae635d4cd input: lifecycle_state is gone; a platform's menu button has a call of its own
lifecycle_state was a 64-bit global in input_driver.c that six files
declared extern for themselves. It was read as a mask of hotkeys held,
one bit tested for every hotkey on every frame, but only one bit was
ever written: Menu Toggle. What it carried is a platform's own menu
button, one that is not a controller's and has no bind - the Home
button of a Wii Remote, the reset button of a GameCube, a tap on a
3DS's bottom screen, the menu key of a handheld.

That is now said in so many words. The driver that reads the button
calls input_driver_set_platform_menu_button(), the state is a field of
the input driver's own, and the hotkey check tests it for Menu Toggle
alone.

- 3DS video driver, Wii and GameCube joypad drivers, the Dingux joypad
  driver: each write becomes the call, one for one.
- PSP joypad driver: it only ever cleared the bit and never set it;
  the clear is removed.
- Switch joypad driver: it declared the variable and did not use it.

Nothing changes in what the button does. Built for Linux and run
through the frontend harness; the Dingux driver is syntax-checked
here. The 3DS, Wii, GameCube, PSP and Switch files are not compiled
here: their change is the same replacement, and CI builds them.
2026-10-05 02:38:33 +02:00
Brad Smith 925e38ad9d Add hatarib to appstore core list 2026-10-05 02:34:13 +02:00
github-actions 553db673e1 Fetch translations from Crowdin 2026-10-05 00:31:35 +00:00
LibretroAdmin a519ec34d3 menu: Find a Button by Pressing It finds the sticks, and scrolls to what it finds
Two things about the entry in a port's controls.

It found the sixteen buttons and not the sticks, whose eight
directions have lines of their own. A stick pushed half way now
counts, as does a key bound to a stick's direction, and the selection
lands on that direction's line. A button is taken before a stick when
both answer.

And it put the selection on the line without scrolling the list to
it. A line past the first screen of them - R2, L3 and R3 already, on
a small menu - was selected out of sight. The list is scrolled to the
line now.

The read is still made only while the screen is up
(input_driver_user_controls_bound(), which was _buttons_bound()).

Run in the Linux build with keys bound to a direction of each stick:
each lands on its line, with the list scrolled to it.
2026-10-05 02:10:23 +02:00
LibretroAdmin e24e7296a0 Vulkan: CPU-written buffers go to video memory where the device maps all of it
Buffers the CPU only writes and the GPU reads - the per-frame UBO and VBO
chains (menu, fonts, display, overlays), the HDR uniforms, the slang
chain's UBO, VBO and LUT staging, the effect VBO, the display meshes and
the quad IBO - take the device-local host-visible coherent memory type
when its heap is larger than a 256 MB BAR window: resizable BAR on a
discrete card, the carve-out on an APU. The GPU then reads them from
video memory instead of across the bus. A refusal there falls back to the
host-visible type chosen as before, and where there is no such heap that
type is all that is tried, so devices without resizable BAR allocate
exactly as they did. Readback, staging and streamed frame textures stay
in cached system memory: a core can be lent a frame texture with read
access, and the CPU reads the others back.

The init log names the type and heap when the placement is in use.
samples/gfx/vulkan_memory_type checks the selection against a card with
a 256 MB and a 214 MB BAR window, the same card with resizable BAR, an
APU and a unified-memory device.
2026-10-05 01:46:59 +02:00
libretroadmin fca0471916 gfx_surface: a single-slot surface borrows two upload slots
A surface of one slot - an APNG overlay, an animated thumbnail decoded
without the worker - borrows both of the driver's upload slots under
direct video and keeps them as one: slots[0] is the buffer to write
next, and the writability check turns to the other while the GPU still
reads the current one. It is double buffered as the driver's own copy
path is, so its frames no longer pass through a copy into that memory
and no more of them wait or drop than before; its own slot memory is
the same size it was. The APNG overlay ticks and the thumbnail's
synchronous decode ask before taking a frame.

threaded_video_test's lend lane runs a one-slot surface too, and holds
vulkan and d3d12 to borrowing both slots for it.
2026-10-05 01:39:20 +02:00
LibretroAdmin 39f64dd580 menu: pulling a trigger binds its axis, not its button
Some triggers are a button and an axis at once: a DualSense's and a
DualShock 4's report a button as soon as the trigger leaves its rest,
and its position on an axis. Binding L2 or R2 by pressing it looked at
buttons first, so such a trigger was bound to its button: on or off,
with its analog position bound to nothing.

When a button answers while L2 or R2 is being bound, the trigger's
axis is now looked for and bound in the button's place. It is told by
two things together: it rests at an end of its range, which a stick
does not, and it has just left that rest, which the other trigger has
not (menu/menu_bind_trigger.h). The rest of that pull is not taken for
the next bind's press. A trigger that is a button alone is bound to
its button, as before, and so is every other control.

samples/input/bind_trigger_axis covers the choice of axis: the
trigger's first step, a full pull, a stick held over, noise at rest, a
trigger resting at the top, an axis resting in the middle. It runs in
CI. The capture itself was not run with a controller.
2026-10-05 01:37:38 +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 da40d50d06 menu: Find a Button by Pressing It, in a port's controls
Port N Controls lists the port's buttons one to a line, and to change
what one does it has to be found in the list first - by name, on a
controller whose buttons may not be named that way.

A new entry at the top of the list waits for a button of that port's
controller, or a key bound to one, and puts the selection on the
button's line. The screen stays up until the button is let go, so
that the press does not act on the list it lands on, and gives up
after five seconds.

It reads the controller as it is bound, before remaps
(input_driver_user_buttons_bound()), and only while the screen is up:
the poll does none of it and is no slower. The plan calls this
listen-to-remap; this is the finding half of it.

The sixteen buttons are found. The stick directions, which have lines
of their own, are not.

Run in the Linux build with the keyboard as port 1's pad: the key for
X lands on X's line; the key for A, which is also the menu's OK,
lands on A's line and does not open it; with no press the list comes
back after five seconds and works as before.
2026-10-05 01:05:23 +02:00
LibretroAdmin 986294d8f5 input: two smaller cuts on the frame's input path
Timed with the cycle counter after the last change, a poll was about
820 cycles, 650 of them the per-user loop: its test for whether a
user has anything remapped walked the 24 controls one by one, for
every user.

- The test is one block compare of the user's row against a row with
  every control mapped to itself. On most polls there is no
  difference to stop at, so a walk that stops at the first one ran to
  the end every time.

- A button read by the core, once the frame's view of the port is
  compiled, no longer loads the three settings that decide when to
  compile it. They are looked at only while the view is not there.

The harness's late poll goes from 0.50-0.59 us to 0.41-0.46 us. The
second change is in its noise, a nanosecond or so on a read of 25.

Nothing a core sees changes; every harness lane passes.
2026-10-05 00:52:23 +02:00
LibretroAdmin fcef366ab3 input: the poll does no remap work for a user with nothing remapped
Nine tenths of what a poll cost was remap work: for every user, every
poll, the whole pad was read - every button, the analog buttons, both
sticks - and then searched for controls mapped to another one. With
nothing remapped, which is how most sessions run, the search finds
nothing and the reads were for nothing. What a core reads of a control
that is not remapped never came from there; it is read on demand.

The poll now asks first whether the user's mapping gives that work
anything to do: a button or an axis mapped away from itself for a pad
or analog device, a key on a control for a keyboard device. If not,
the pad is not read, and what the last poll that did have work left in
the mapper is cleared once. The test is a walk over 24 numbers a user.
A mapping that only switches controls off still counts as work, and
costs what it always did.

Timed with the cycle counter on the harness, before: 2900 cycles a
poll, 2680 of them the remap work. The harness's late poll goes from
0.95-1.12 us to 0.48-0.54 us, with one pad and with eight.

A harness lane pins what the core sees: nothing mapped, a button
mapped to another, one switched off, and the mapping taken away again
with the button held, read by mask and button by button; and that the
work runs only while something is mapped.
2026-10-05 00:34:19 +02:00
LibretroAdmin e29c83c3b8 input: the poll makes no sensor snapshot when no shader reads one
Every poll read the six sensor values and published them as a
snapshot, with the sensors setting on - its default - whether or not
anything read sensors. The snapshot has one reader, a shader that
uses the sensor uniforms, and almost no session has one.

The poll now makes the snapshot only while such a shader is loaded.
When it goes, noughts are published once and nothing after. What is
left on an idle poll is the test itself: one setting, one flag, two
rates. Turning the sensors on and off at the driver, and the
accelerometer's rest capture, are as before; the rest capture is no
longer a call on polls where it is not running.

This is the first of the plan's liveness tests: the poll does not
call into a feature that is idle.

A harness lane pins it: no snapshot with no shader, one every poll
with one, noughts once when it stops. The work removed is six reads
and a twelve-store publish a poll; the harness's poll timing does not
resolve it - 0.89 to 1.03 us after against 0.95 to 1.01 us before, in
its noise.
2026-10-05 00:12:36 +02:00
libretroadmin ef17416e6c d3d12: streamed textures lend their upload memory
D3D12 answers texture_lend and texture_lend_ready, so under direct
video an animated thumbnail's frames are decoded into the upload
buffers its draw-time copy reads from, without the copy into them.
Slot 0 is the texture's upload buffer and slot 1 a second one of the
same layout; once lent each stays mapped, and is guarded by the fence of
the frame that last copied from it. A submit from a lent slot only
points the next draw-time copy at it. A lent slot is writable once its
last copy has run and no copy from it is still to be recorded, so a
frame is never written under the draw that copies it. An update from
other memory goes through the buffer not lent. A texture that lends
nothing uploads exactly as before, as does every texture whose row
pitch is not the producer's - the footprint pads rows to 256 bytes.

threaded_video_test holds d3d12 to lending as it does vulkan.
2026-10-04 23:59:29 +02:00
LibretroAdmin bca6a306c1 android: the input driver asks the video driver, and does not reach into it
The Android input driver handles the app's lifecycle commands, and for
that it took the video driver's state in seven places: to let the
context's surface go when the window is taken away, to give it a new
one when the window comes back, to ask whether the context is the
Vulkan one, and to read the video driver the configuration names while
a core has forced another.

Those are functions of the video driver now, and the input driver
calls them:

  video_context_surface_create(), _can_create(), _destroy()
  video_context_driver_is()
  video_driver_get_configured_ident()

The code is the same code, moved: the surface calls are still handed
to the video thread under threaded video, and the destroy still waits
for the frame that may be using the surface.

With this nothing under input/ calls video_state_get_ptr(), nothing
under gfx/ calls input_state_get_ptr(), and nothing under gfx/ names
an input driver. tools/input_video_separation_check.py's two lists
are empty, and CI fails anything that goes back in.

Compiled as the Android build compiles it - the single-file build,
with the NDK - with no error and no new warning. Not run on a
device.
2026-10-04 23:51:31 +02:00
libretroadmin 71f78e5a6d gfx_surface: animated thumbnails decode into the driver's upload memory
Under direct video a driver can lend a streamed texture's upload
memory, through two optional poke entries: texture_lend hands out a
slot laid out at the pitch the caller writes, and texture_lend_ready
answers, without waiting, whether the GPU is done reading it. Vulkan
lends its stream state's mapped staging buffers; an update whose image
lies in a lent slot copies from there with no memcpy, and the copy path
only ever uses the slots not lent. A gfx_surface of two slots or more
lends a slot after its first submit, so the thumbnail animation jobs
decode each frame straight into the memory the GPU uploads from; a job
takes its slot's pointer when it starts, and starts only once the slot
is writable. The surface's own memory is back before its texture is
replaced or freed. The thread wrapper and the other drivers lend
nothing, and single-slot surfaces keep the driver's double buffering.

threaded_video_test checks on a real driver that direct video lends and
updates the same texture from lent memory and threaded video lends
nothing; gfx_thumbnail_anim_test's stubs keep a lent slot busy after
each upload and catch any frame written early or submitted stale.
2026-10-04 23:51:06 +02:00
LibretroAdmin d0d2895885 android, web, qnx, uwp: the frontend starts the input driver
The last six video contexts that started an input driver themselves:
Android's two, the web's two, QNX's and UWP's. Each started its
platform's one input driver, whatever the setting said; they now leave
it to the frontend, which does the same (input_driver_init_platform()).

UWP's choice between its two drivers goes with it: xinput when that is
what the setting says - it is joypads only - and the uwp driver
otherwise.

With threaded video the input driver is now started on the frontend's
thread, where it is polled, and not on the video thread.

No file under gfx/ names an input or a joypad driver any more:
tools/input_video_separation_check.py's list for that is empty, and a
file that starts naming one fails. What it still lists is the Android
input driver's seven calls into the video state.

None of these six files can be built here: checked by reading, by
compiling the frontend's choice under each platform's build condition,
and by CI's builds.
2026-10-04 23:25:20 +02:00
LibretroAdmin a0aa1296c4 consoles: the frontend starts the input driver
The video drivers of the consoles - 3DS, GameCube and Wii, Wii U, PS2,
PS3, PSP, Vita, Switch - each started their console's input driver
themselves and named it. They now say only that the platform has an
input driver of its own, and the frontend starts it
(input_driver_init_platform()): that driver, whatever the setting
says, as before.

Which driver that is is told from the same build conditions
input_drivers[] lists each one under.

One detail on the PS3: the RSX driver passed the PS3 joypad driver's
name where the others pass the joypad setting. The frontend passes the
setting, and a name that is not built in falls back to the first
joypad driver that starts, which there is the PS3's.

tools/input_video_separation_check.py lists ten files fewer: 6 are
left that name an input driver.

None of these files can be built here: checked by reading, and by
CI's console builds.
2026-10-04 23:25:16 +02:00
LibretroAdmin 480b576b84 uwp: win32_window_client_dims() has a UWP side
The D3D10, D3D11 and D3D12 drivers now ask win32_window_client_dims()
for the size to make the swap chain. That function lives in
win32_common.c, which the UWP build leaves out, so the UWP link failed
with an unresolved external.

uwp_main.cpp already carries the UWP side of the other win32_*
helpers; this one joins them. It returns the size asked for: the
swap chain is made on the CoreWindow, and TryResizeView() reports the
view's real size later with the resize event, as before.
2026-10-04 23:18:02 +02:00
LibretroAdmin 0af2ec5f96 sdl: the frontend starts the input driver for the SDL 1.2 video drivers
The SDL 1.2 video driver and the two handheld ones (Dingux, RS-90)
started the input driver themselves and named it.

The SDL 1.2 driver always started the SDL 1.2 input driver, whatever
the setting said: the window's keyboard and mouse come through SDL's
event queue. It now says it put up an SDL 1.2 window and the frontend
does the same (input_driver_init_sdl1()).

The handheld drivers started the driver the setting named, for the
four it can be there (sdl_dingux, sdl, udev, linuxraw), and left any
other to the frontend - which starts the driver the setting names. So
they now leave all of it to the frontend, and their copy of the choice
goes.

Nothing changes in which driver any of them gets.

samples/input/window_driver gains a second build, with SDL 1.2 in SDL
3's place, for the SDL 1.2 window's choice; CI runs both.
tools/input_video_separation_check.py lists three files fewer: 16 are
left that name an input driver.

All three video drivers compile against SDL 1.2 here; none was run.
2026-10-04 23:05:45 +02:00
LibretroAdmin c609578994 runloop: the core preload stubs are built without HAVE_DYNAMIC
The runloop_core_preload_* fallbacks sat inside the HAVE_DYNAMIC
block, so static builds (DOS, GameCube, Wii, 3DS and the other
console ports) had no definition and task_content.c failed to link.
They now live after that block, under
!(HAVE_DYNAMIC && HAVE_THREADS).
2026-10-04 22:54:14 +02:00
LibretroAdmin f807dbdc55 d3d10, d3d11, d3d12: the swap chain is made the size the window is
After a driver restart on a kept window the picture could be stretched
for a frame or two, most visibly with the statistics on.

The drivers made their swap chain the size they had asked the window
to be. The window is not always that size: Windows does not let one
grow past the screen, and a menu bar takes its share. The chain was
then presented stretched over the window until the first resize
notice had been acted on, two frames later. With a new window nobody
saw those frames; with the window kept across the restart they are on
screen. A core option that raises the internal resolution asks for a
window larger than the screen readily.

The drivers now take the window's real client size once it has been
set up (win32_window_client_dims()) and make the swap chain that
size, so the first frame is right. Nothing is added to a restart or a
fullscreen toggle: it is one GetClientRect.

Run with D3D11 under Wine, with a core that doubles its resolution on
a button press and the window held smaller than asked for: before,
the first two frames had a 1440x1080 back buffer on a 1440x647
window; now the first frame is 1440x647. A fullscreen toggle by
restart is unchanged. D3D10 and D3D12 are compiled, not run.

Reported by sonninnos.
2026-10-04 22:53:00 +02:00
LibretroAdmin 98d54bd3e7 video: a driver restart keeps video_driver_is_hw_context() true
After any driver restart under a core that renders with the GPU - a
fullscreen toggle, a core option that changes the internal resolution
- video_driver_is_hw_context() returned false, though it was true at
start-up and the core was still rendering with the GPU.

A restart saves the core's hardware-render request, tears the drivers
down, and puts the request back. The teardown clears the request and
the published copy of its type (video_driver_free_hw_context());
putting it back restored the request and not the published copy,
which is the only thing video_driver_is_hw_context() reads. A core
that asks for its context to be cached was not affected: the teardown
leaves both alone for it.

The published copy is now put back with the request, before the
drivers are built again, so the build sees it as well.

The frontend harness gains a lane: a hardware-render request in
place, a driver restart, and the request is back, the core's context
was destroyed once and reset once, and the frontend still knows it
has a GPU-rendering core. The lane fails without the fix.

Reported by sonninnos.
2026-10-04 22:20:58 +02:00
LibretroAdmin 2a0c7c1ae6 content load: the core's library is opened on the task worker
A staged load opens the new core's library on the task worker once the
old core is closed, while the frame loop goes on, and the core stage
takes the handle instead of opening it itself. Only the open moves;
retro_init, the content read and retro_load_game stay on the thread
that runs the core. The open waits for the old handle's release, so a
same-core reload still comes up on its own statics, and a generation
counter drops a handle whose load was superseded. Without a task worker
the core stage opens the library itself, as before.

The content_load harness checks the library is opened on a worker, not
the frame-loop thread, and that a same-core reload comes up fresh.
2026-10-04 22:04:15 +02:00
libretroadmin 8b713d1cce d3d8, d3d9, d3d10: textures are updated in place
The three drivers answer update_texture, so a streaming surface - an
animated thumbnail, an APNG overlay - keeps one texture and rewrites it
each frame instead of creating and releasing one. D3D8 and D3D9 lock
level 0 of the managed-pool texture their load made, which writes the
runtime's system copy without waiting on the GPU. D3D10 maps the staging
texture its load made with DO_NOT_WAIT and copies it across, dropping
the frame rather than stalling when the GPU still has the last one.

D3D8's texture load and unload reach the video thread wrapper only
under HAVE_THREADS, as every other driver's do, so a build without
threads links. The compile matrix builds every D3D driver without
threads as well, where a wrapper call outside HAVE_THREADS shows up as
the implicit declaration it is.
2026-10-04 22:03:51 +02:00
LibretroAdmin a647440f58 wayland: leave fullscreen before sizing the window
Turning fullscreen off made RetroArch quit when the window was taller
or wider than the screen:

  xdg_surface geometry (960 x 720) is larger than the configured
  fullscreen state (1024 x 640)

The window is kept across the reinit a fullscreen toggle does, so it
is still fullscreen when the windowed mode is set. The windowed size
was put on the surface first and fullscreen was left afterwards.
While the compositor still holds the window fullscreen, a size larger
than the screen in either direction is a protocol error, and the
compositor closes the connection.

Fullscreen is now left first, and the compositor's answer waited for,
before the windowed size goes on the surface.

Under a headless Weston and a headless Mutter with an output smaller
than the window, fullscreen toggled four times with Vulkan and with
GL, threaded video on and off: RetroArch keeps running. The same on
outputs larger than the window, as before.
2026-10-04 22:03:16 +02:00
libretroadmin 572862581a vulkan: a streamed texture update allocates nothing
Each slot of a texture's in-place update state owns the command buffer
that copies its staging buffer into the texture, made with the slot.
The slot's fence already guards the staging buffer for reuse, and now
guards the command buffer the same way: an update to a free slot resets
and re-records it and submits it on that fence, with no command buffer
allocated and no deferred-release node per frame.
2026-10-04 15:28:34 +02:00
libretroadmin 22fe187bbd gfx: 10-bit textures go to every texture path that takes them
Whether a pix10 image is uploaded as 10-bit is now the texture path's
own answer, TEXTURE_GPU_FORMAT_RGB10A2 through supports_texture_format,
rather than GFX_CTX_FLAGS_SCREEN_10BPC_SOURCE, which says whether a
context presents 10-bit core frames. D3D11, D3D12, Vulkan and Metal
answer it as before; gl2 (desktop) and gl3 now load and update a pix10
image as RGB10_A2 from the same packed words their core frames use, and
answer it too, where they used to read those words as 8-bit texels.
GLES2, gl1 and D3D8-10 decline and get the image at eight bits. A static
Vulkan texture gets its mip chain only where the format can be blitted
with linear filtering, which packed 10-bit formats often cannot, and one
level otherwise. gfx_surface's requirements and narrowing and
video_driver_texture_load() all ask the same thing.

Every shader LUT loader and the image viewer core clear the 10-bit ask
before image_texture_load(), which read it from an uninitialised field.
threaded_video_test's external surface lane has the texture back end
answer both ways and checks that 2101010 is offered exactly when taken,
and on a real driver that takes it loads, updates and reloads a 10-bit
still.
2026-10-04 15:28:30 +02:00
LibretroAdmin 5424cd21c9 win32 modeline resync: the re-plug wait takes no lock
The helper that waits for the driver to re-plug the monitor kept its
two notifications and the thread's "window is up" flag under a mutex,
with a condition variable for the waits.

Each of the three is an atomic word now, stored before an eventcount
is notified. The wait for the window and the bounded wait for the
re-plug sleep on that eventcount; arming the helper is two stores. The
5 s bound and what happens when it runs out are as they were.

display_servers_win32_vendor_test already pins this under Wine: the
mock driver re-plugs the monitor after a forced set, and the flush
must return on the notifications rather than on the timeout. It does,
in 3 ms. That sample and display_servers_win32_live link
retro_eventcount.c now.
2026-10-04 15:27:41 +02:00
LibretroAdmin 7162eeaf1b wayland: the window is tagged once libdecor has made it
With threaded video on GNOME, RetroArch exited at start-up:

  Failed to load plugin 'libdecor-gtk.so': failed to init
  No plugins found, falling back on no decorations
  error marshalling arguments for set_toplevel_tag (signature os):
  null value passed for arg 0

libdecor makes the window's xdg_toplevel only once the compositor has
answered its own start-up. A decoration plugin's start-up waits for
that as a side effect, so with a plugin loaded the toplevel is there
straight after libdecor_decorate(), and the code relied on it. With
no plugin it is not there until the first dispatch, and the NULL was
handed to the icon and tag requests. libwayland does not recover from
that: the connection is dead and RetroArch exits.

No plugin is what there is when only the GTK one is installed and the
context starts off the main thread, as it does with threaded video:
that plugin refuses to start there. And the tag request is only sent
where the compositor has the tag protocol, which recent GNOME does.

The icon and the tag are now set as soon as the toplevel exists: at
once if it does, otherwise after the window has been configured,
which is still before anything is drawn to it.

Under a headless Mutter with libdecor given no plugin, the toplevel
was NULL before the window was mapped and there after it was
configured, threaded or not. That Mutter has neither protocol, so the
requests themselves were not run here.
2026-10-04 15:18:15 +02:00
LibretroAdmin 9bb40d236e tpool: the fallback pool joins its threads too
The pool that backends without lock-free atomics get still detached
its worker threads, and its tpool_destroy() still returned once each
had counted itself out under the mutex - before the thread had ended.
It is the window 8cb8a02 closed for the lock-free pool: the caller
went on, to free what the jobs used or to leave the process, with pool
threads still on their way out.

The threads are kept and tpool_destroy() joins each one, as the
lock-free pool does. The exit count and its condition are gone, and
tpool_wait() no longer takes the mutex at teardown.

tpool_wait_test's "no worker outlives tpool_destroy" case fails
against this pool as it was (3 of 4 workers had ended) and passes now.
Nothing in CI built this pool, so the samples target runs
tpool_wait_test a second time with RETRO_ATOMIC_FORCE_VOLATILE, under
ASan and UBSan; not under TSan, which rightly objects to a volatile
int standing in for an atomic.
2026-10-04 15:15:24 +02:00
LibretroAdmin 8cb8a028a3 tpool: tpool_destroy joins its threads
The lock-free pool detached its worker threads and tpool_destroy()
returned once each had counted itself out under the mutex. A thread
does that before it has ended: it still has the mutex's unlock to
finish and its own exit to run. So tpool_destroy() could return, free
the pool and hand control back - to a caller that frees what the jobs
used, or leaves the process - with pool threads still on their way
out.

The win32-waits-check lane showed a crash of that shape after the
lock-free handoff went in: tpool_wait_test as a Windows binary under
Wine passed every test, printed its last line and then took a
segmentation fault as the process exited. That crash did not reproduce
here in over a hundred runs on a one-CPU box, so this is the fix for
the window the crash sits in, not a confirmed fix for the crash.

The threads are kept and tpool_destroy() joins each one. With that the
exit count, its condition and the lock a worker took on its way out
are gone, and tpool_wait() takes no lock at all; the mutex is left for
the overflow list alone. The lock budget for tpool_worker and
tpool_wait drops from 1 to 0.

The pool for backends without lock-free atomics is unchanged.

tpool_wait_test gains a case for POSIX threads: each worker is marked
with a thread-specific value whose destructor runs as the thread ends,
and every destructor must have run when tpool_destroy() is back, over
300 pools. Against the pool as it was, it fails within the first
hundred with "3 of 4 workers had ended when tpool_destroy returned".
2026-10-04 15:12:01 +02:00
LibretroAdmin 14ce75bdd9 wayland: say why when the compositor closes the connection
A Wayland compositor that finds a protocol error closes the client's
connection. RetroArch notices the hang-up and quits as if it had been
asked to, with nothing in its log: libwayland prints the error on
stderr only. From a log file, RetroArch "just exits".

The hang-up now logs the reason before quitting: the protocol error's
code and the object it was raised on, or the system error otherwise.

  [Wayland] The compositor closed the connection: protocol error 4 on
  xdg_wm_base@18. Quitting.

Reproduced under a headless Weston whose output is smaller than the
window: leaving fullscreen is a protocol error there. That bug is not
fixed here; this only makes it, and any other, show in the log.
2026-10-04 15:09:14 +02:00
LibretroAdmin 29de8e1c9c sdl3: the frontend starts the input driver
The SDL 3 video code chose and started the input driver for its
window itself, and named five input drivers to do it. It now says
only that it put up an SDL 3 window, and the frontend makes the same
choice (input_driver_init_sdl3()): the configured driver if it is one
that reads devices itself - udev, linuxraw, raw, dinput - and it
starts; the SDL 3 input driver otherwise.

The event pump asked whether the SDL 3 input driver was the one in
use by naming it; it asks input_driver_is_sdl3() now.

Nothing changes in which driver an SDL 3 window gets. With threaded
video the input driver is now started on the frontend's thread, not
the video thread.

samples/input/window_driver covers the SDL 3 window's choice, on the
host and under Wine, and the Wayland surface's from the last change.
tools/input_video_separation_check.py lists one file fewer: 19 are
left that name an input driver.

Run here against SDL 3.2 with the SDL 3 video driver under a virtual
X display: keys drive the menu with the input setting at x, with it
at udev (which cannot start here, so the SDL 3 driver takes over),
and with threaded video.
2026-10-04 14:59:32 +02:00
LibretroAdmin 20c84f9509 mutter display config: callers read the worker's state without a lock
The worker that follows Mutter published its state under a lock, and
every caller took that lock: to ask whether Mutter is there, to build
a resolution list from the state, to check a switch against it, and to
queue the switch for the worker.

With pointer atomics there is no lock. The worker publishes a state by
swapping the pointer. A caller that reads the state counts itself in
before loading the pointer and out when it is done, and the worker
frees the state it replaced only once that count is zero, waiting on
an eventcount the last reader notifies. Asking whether Mutter is there
is one load. Switch requests are pushed on a list the worker takes
whole, oldest first; with more than eight waiting, the newest eight
are the ones applied.

A switch is checked against the state and then queued, no longer under
one lock; the worker looks the mode up again when it applies it, as it
always did.

Backends without pointer atomics keep the lock, the fixed queue and
the old order.

mutter_displayconfig_test gains a case: four threads read the list
while the rate is switched back and forth sixty times, so the worker
replaces the state under them. Every list must be whole. CI runs it
under ASan with the rest, and builds the test a second time under TSan
for the cases that need only the session bus (run.sh BUS_ONLY=1);
freeing the replaced state without waiting for the readers is a TSan
data race there. The sample links retro_eventcount.c now.
2026-10-04 14:54:08 +02:00
Rob Loach 8978c2c35e sdl3: Remove unneeded changelog 2026-10-04 14:44:28 +02:00
Rob Loach c73a8f8600 sdl3: pump events on one thread 2026-10-04 14:44:28 +02:00
LibretroAdmin 108269ee10 wayland: the frontend starts the input driver
The two Wayland contexts started the Wayland input driver themselves
and named it. They now leave it to the frontend, as the Windows, X11
and windowless paths do.

Wayland differs in one way: the seat's state - keyboard, pointer,
touch - lives with the surface, in the video context's own data. So a
context can hand that state over along with the kind of window
(input_driver_left_to_frontend_with()), and the frontend starts the
Wayland input driver on it (input_driver_init_wayland()). With
threaded video the input driver and the joypad drivers are now started
on the frontend's thread, not the video thread.

Nothing changes in which driver a Wayland session gets.

tools/input_video_separation_check.py lists two files fewer: 20 are
left that name an input driver.

Run under a headless Weston with the GL context, with and without
threaded video: the Wayland input driver is found and RetroArch runs.
Keys could not be sent there, and the Vulkan context was compiled but
not run.
2026-10-04 14:40:24 +02:00
LibretroAdmin b486da62aa input: the frontend starts the input driver for five windowless video drivers
The sixel, network, HUB75 and Odroid Go video drivers and the Go2 DRM
context each started the udev input driver themselves. They now say
only that there is no window system and leave the input driver to the
frontend, as the DRM and Vulkan display contexts already do.

What that changes for them:

- udev is started when the input driver setting is udev or x, as
  before. With any other setting the frontend now starts that driver;
  before, they started udev whatever the setting said.
- If udev cannot start, linuxraw is tried before giving up.
- The sixel driver passed the input driver's name where the joypad
  driver's name belongs. The frontend passes the right one.

tools/input_video_separation_check.py lists five files fewer: 22 are
left that name an input driver.

sixel, network and HUB75 compile here; the two Odroid files need
libraries this box does not have and were not compiled.
2026-10-04 14:18:25 +02:00
LibretroAdmin 256c8b9801 linux illuminance sensor: the poll thread waits without a lock
The sensor's poll thread waited between readings on a condition under
a mutex, which a rate change and close took to wake it, and which the
thread took and dropped around every reading to look at the rate and
the two flags.

The rate, a count of rate changes and the done flag are an atomic
word each, and the thread waits on an eventcount with the time to its
next reading as the bound. A rate change stores the rate, bumps the
count and notifies; close stores done and notifies. The thread reads
the count before the rate, so a change landing between the two is
read again rather than missed.

The behaviour is the one illuminance_sensor_test pins: a new rate
applies at once, and close returns as soon as a read in progress
does. The sample links retro_eventcount.c now.
2026-10-04 14:17:50 +02:00
LibretroAdmin 8fc5e52783 cloud sync: entries reach the updated manifests without a lock
Completion callbacks for fetches, uploads and deletes add entries to
the two updated manifests from whatever thread the driver calls back
on, as the task thread does for the entries it settles itself. Each
append took a process-wide lock.

An entry is now a record pushed on a lock-free list in the sync's own
state, and the manifests are the task thread's alone: it folds the
records in when the diff is over and nothing is in flight, which is
the first time a manifest is read. A callback pushes before it drops
`waiting`, so the fold sees every record. The manifest is sorted
before it is written, so the order the records arrive in does not
matter. A sync that ends early folds them in before freeing the
manifests, so what was added is freed as it was.

An append that could not allocate used to write through a list that
had not grown; it now marks the sync failed. Backends without pointer
atomics keep the lock.

cloudsync_manifest_adds_test adds entries from four threads and the
task's own at once and checks each is in its manifest once, with its
own hash, under ThreadSanitizer and ASan.
2026-10-04 13:58:45 +02:00
LibretroAdmin 0a71caf5c6 x11: the frontend starts the input driver for every X11 video path
Three X11 video paths still started the X11 input driver themselves:
the EGL context (egl_x) and the xvideo and xshm video drivers. They now
say only that they put up an X11 window and leave the input driver to
the frontend, as the GLX and Vulkan contexts already do.

One thing changes for them: with the input driver set to udev they
now get udev, as the other X11 paths do. Before, they always got the
X11 driver whatever the setting said. The default on a build with X11
is the X11 driver, which is unchanged.

tools/input_video_separation_check.py pins what is left of the video
and input code reaching into each other: gfx/ calling
input_state_get_ptr() (none), input/ calling video_state_get_ptr()
(Android's lifecycle commands), and gfx/ naming an input or joypad
driver (27 files: the consoles, SDL, Wayland, Android, the web, and
the windowless Linux drivers). The lists are in the check and only
shrink. It runs in CI with its self-test.
2026-10-04 13:58:09 +02:00
LibretroAdmin 60068a2886 samples: udev_rumble_test reads its stand-in's notes atomically
CI's thread sanitizer run failed: the test's upload stand-in notes what
it was given on the writer's thread, with atomic stores, and three of
the test's checks read those notes plainly on the main thread. The
race was in the test, not in the driver.

Every read goes through an atomic load now. A message that a
search-and-replace had mangled ("and N(uploads) nothing") is put
right.
2026-10-04 13:49:16 +02:00
LibretroAdmin 3295d0f5b0 close content: the core's button descriptions outlive the core
Closing content from the menu could drop every disabled button from
the remap file (issue #19698).

A remap file keeps a disabled button ("-1") only if the core described
that button; one the core never described is not written. The
descriptions are part of the core's system information.

CMD_EVENT_UNLOAD_CORE saves the remap, starts the load of the dummy
core, and frees the system information. Since content loads became
staged (2db2e86ba7) the old core is closed a frame later, not inside
that call, so the system information was freed with the core still up.
That mattered when a configuration override was in use: unloading the
override loads the remap file again, so the core's own deinit saved it
a second time - by then with no descriptions, and every disabled
button was dropped. Quitting RetroArch did not go that way.

The system information is now left for the core's deinit to free,
after it has saved, when a staged load is closing the core.

The frontend harness gains a lane that follows the report: a disabled
button saved to a core remap, Close Content, the core started again
from the menu, and Close Content with an override in use. Its core now
describes its buttons at load. The lane fails without the fix.
2026-10-04 13:49:11 +02:00
LibretroAdmin 5219cf3ca3 Wii U audio: the frame count is atomic, the spinlock is gone
The AX frame callback and the writer changed `written`, the count of
frames buffered, under an uninterruptible spinlock - interrupts off
on both sides - while start(), the write's own room check,
write_avail() and wait_writable() read it with no lock at all. The
two played and underrun totals sat under the same spinlock as 64-bit
counters, read back through it and returned as a size_t, which on
this CPU is their low word.

written is a retro_atomic_int_t now: the writer adds and the callback
subtracts in one atomic step each, and everything else loads it. The
totals are a word each, which is what the interface reported anyway,
written only by the callback. Nothing takes the spinlock, so it is
removed, and the 3 ms callback no longer finds interrupts off for a
writer's sake.

wiiu_ax_test builds the driver against the tree's Wii U headers with
stand-in AX and OS functions, runs the callback and the writer on two
threads and checks that every frame accepted is buffered or counted
played. Under ThreadSanitizer the driver as it was reports the
unsynchronised read of written; this one is clean.
2026-10-04 13:40:35 +02:00
LibretroAdmin 290559c89f gfx_thumbnail: the animation job queue takes no lock
The decode worker's queue was a linked list under a mutex, with two
condition variables: every job posted, every job taken and every
frame finished went through the lock, and so did closing a thumbnail.

The main thread now posts a job by pushing it on an inbox with a
compare-and-swap; the worker takes the inbox whole and works through
it oldest first, parking on an eventcount when it is empty.

A job released while it waits can no longer be unlinked - the worker
may be holding its place in the queue - so it is marked cancelled,
and its block is kept on a list until the worker has come to it and
let it go unrun. Freed any sooner, the worker would walk into freed
memory. A job that is decoding is waited out as before, on an
eventcount the worker notifies. Closing a thumbnail therefore still
returns with its stream untouched by the worker, and no longer waits
for the lock.

At deinit, after the join, jobs still queued go back to their
thumbnails and cancelled ones are let go, so nothing is left waiting
on a worker that is gone. Backends without pointer atomics keep the
locked queue.

gfx_thumbnail_anim_test closes 480 animations with a job waiting and
checks the next one still animates; freeing a cancelled job's block
at once is a use after free ASan reports there.
2026-10-04 13:30:05 +02:00
libretroadmin 83960f094e audio_transfer: FLAC resumes after running dry, windowed WebM opens behind its wall
A FLAC read that finds nothing more to hand the decoder stops there and
latches nothing, so a demuxed packet set grown after a starved read, and
a windowed WebM whose wall is raised, carry on at the next read as the
other arms do; rflac keeps any frame split across the boundary until the
rest arrives. The WebM arm keeps an avail given before start() and opens
its demuxer behind it, rather than walking every cluster header through
pages a windowed caller has not made resident.

libretro-common/samples/formats/audio_transfer_flac decodes lossless
fixtures through every input mode - native buffer, Matroska whole and
windowed, demuxed whole and grown a packet at a time - and compares each
sample and the frame count with the PCM they were encoded from.
2026-10-04 13:15:42 +02:00
LibretroAdmin 1d5dc0cadf winraw: Controller Player Lights turned off puts the lights out
With the setting turned off again the lights stayed as they were last
set, until the controller was plugged in again.

The lights this driver lit are now put out when they are no longer
wanted: the setting turned off, or the controller on no port. A
DualSense's player lights go dark and a DualShock 4's light bar goes
off. Lights the driver never lit are still left alone, so with the
setting off from the start nothing is asked of them.

sony_pad_output_report() takes a player below nought to mean that: the
lights asked for, with nothing lit.
2026-10-04 13:13:05 +02:00
LibretroAdmin 597a7f8ebc tpool: jobs are handed over without the lock
Every job took the pool's mutex twice: once to go on the linked
queue, once to come off it, with a node allocated and freed around
it. Producers, workers and a caller helping from tpool_help() all met
on that one lock.

On a backend with real atomics the queue is now a ring of 256 slots
that producers and workers claim with a compare-and-swap. A worker
with nothing to do parks on an eventcount of its own, and a post
wakes one parked worker rather than all of them: the poster counts
the job and then looks for a parked worker, the worker marks itself
parked and then looks at the count, both sequentially consistent, so
neither can miss the other.

tpool_add_work() still never refuses a job for lack of room. Past
256 waiting, jobs go on a list under the mutex, and the ring is left
to empty ahead of them, so one producer's jobs still run in the
order posted. The mutex is otherwise only taken by a worker as it
exits at teardown. Backends without real atomics keep the locked
queue as it was.

tpool_handoff_test builds tpool.c with yields at the steps whose
order matters and checks no job is left unrun, run twice or run out
of order. It found one such order during this work - a worker picked
for a wake-up before its wait had opened - which is fixed here.
tpool_bench times short jobs through the pool; on the one core this
was written on it shows no change, and says nothing of contention.
tpool_help drops from one lock acquisition to none and the budget
reference moves with it.
2026-10-04 13:13:01 +02:00
LibretroAdmin 2b3487479e audio_mixer: the mix never waits for a voice
Each voice had a mutex. The mix held it across a voice's decode, and
play and stop held it while they built or tore down the decoder - so
the audio thread stopped dead behind a caller opening a stream, and
that is a stall you can hear.

A voice now has an owner word, taken with a compare-and-swap: the mix
while it decodes and mixes the voice, a caller while it plays or
stops it. The mix does not wait for it. A voice a caller holds is
half built or half gone, with nothing in it to mix, so the mix passes
it over and comes back next pass.

A caller still waits, but only for the pass in progress on that one
voice, parked on an eventcount the mix notifies as it lets go - which
costs no lock and no syscall with nobody parked. So stop still
returns with the voice released, and a sound can be destroyed right
after it as before.

Backends whose compare-and-swap is not atomic keep the lock per
voice, and the mix try-locks it there. HAVE_THREADS builds of the
mixer on a lock-free backend now need rthreads/retro_eventcount.c.

audio_mixer_voice_threads_test holds a voice the way a caller does and
checks the mix returns without it, then plays and stops voices on one
thread while another mixes, under ThreadSanitizer and ASan.
2026-10-04 12:45:56 +02:00
LibretroAdmin 8b8ce83922 winraw: Controller Player Lights
New setting "Controller Player Lights" (input_winraw_player_lights,
off by default, Windows raw input joypad driver): each controller shows
the port it is on on its own lights, as the consoles do. A DualSense
lights its player lights; a DualShock 4 colours its light bar blue,
red, green or pink. The lights follow the controller when its port
changes.

The output report that carries the motors' strengths can ask for the
lights as well: sony_pad_output_report() takes a player number, and
with none the lights are left alone, as before. The driver's poll
looks now and then at which port each controller is on, and the
writer's thread sends the report when that changes.

Off, nothing is asked of the lights. Turned off again, they stay as
they were last set until the controller is plugged in again. Xbox
controllers' lights are XInput's and are not touched.

Covered by the report builder's test and the raw input rumble test
under Wine. Not run on a real controller.
2026-10-04 12:45:38 +02:00
LibretroAdmin b60ed132f5 gfx_widgets: task updates go to the widgets' owner, not under its lock
A task's progress push took the widget state lock on the main thread
and made, measured and updated the task's widget there, behind
whatever frame the video thread was drawing. The widget and the task
pointed at each other, so freeing either meant reaching into the
other from the wrong thread at the right moment - most of the
comments this removes were about that.

The main thread now sends what the task looks like - title or error,
progress, flags, style - and nothing else. The owner applies the
updates once a frame, before it touches the queue: it finds the
widget by a key, makes one if the task has none, and measures it with
the font it owns. The key is a number the task carries in
frontend_userdata, so nothing holds a pointer to a task or expects
one to a widget; a widget freed or a task retired is simply not
found. gfx_widgets_task_transfer() moves the key and tells the owner,
which re-keys the widget before the old owner's finished state can
start it expiring.

Updates travel on a lock-free list the owner takes whole. Progress is
sent every frame and gives way when the owner falls behind; a
finished task's last update is always sent. Task widgets wait for
room on the owner's side, and a stamp keeps their order against plain
messages, which stay on the pending ring.

The state lock remains for layout, fonts and lifecycle, and on a
backend without pointer atomics it guards the update list.

The task-gate sample now runs titled tasks to retirement against an
owner thread under ThreadSanitizer; the ownership and bar-stack tests
follow the updates to the owner.
2026-10-04 12:29:17 +02:00
LibretroAdmin 27ef9955d1 input: one thread for the joypad drivers' writes to their controllers
Three joypad drivers each carried a thread of their own to write
rumble from, off the frame loop: Windows raw input, udev and XInput.
Each had the same loop, wake, quit flag, start and stop.

They are now one, input/common/output_writer.c: a thread that is woken
through an event (Windows) or a pipe and calls the driver back. Nothing
passes through it; what is wanted of each controller stays in the
driver's own atomics, and the driver's callback compares that with
what it last wrote. There is no lock. One final call as it is freed
lets the driver let go of what that thread holds.

What each driver writes, and when, is unchanged. The raw input driver's
thread was started whether or not the build had threads; it now needs
HAVE_THREADS, as the other two do, which every Windows build has.

The three drivers' tests run through the helper, udev's under TSan
too; the samples that include these drivers link it.
2026-10-04 12:28:20 +02:00
LibretroAdmin e998d8f31b runahead: key events reach the second instance too
The second instance's keyboard callback is kept in a slot of its own,
and a key event bound for the core goes to both instances, so a key
that arrives while the second instance polls takes effect in the frames
it runs ahead. The slot is emptied before the copy is closed.

The content_load harness checks a key event reaches both instances and
that closing the second instance drops its callback.
2026-10-04 12:18:32 +02:00
LibretroAdmin f451993965 rewind: the buffer a load asks for is set up on the frame after
The drivers stage marks the rewind buffer pending instead of setting it
up, and runloop_check_state sets it up on the next frame, before the
core runs, so the full serialize it takes is no longer part of the load.
Rewind init and deinit clear the pending mark.

The content_load harness checks no frame of a load sets up the buffer
and the first frame after does.
2026-10-04 12:18:28 +02:00
LibretroAdmin 3e8307ca8f audio_mixer: the windowed feeder's position and bound take no lock
audio_mixer_voice_buffer_tell() and audio_mixer_voice_set_avail() took
the voice lock, which the mix holds across a voice's decode and
resample: the feeder waited behind every decode, and the audio thread
behind every feeder call.

The mix publishes the decoder's byte position after each pass, and
the feeder reads that. The feeder publishes the bound it wants, and
the mix hands it to the decoder before its next decode. The decoder
itself stays the mix's alone, under the voice lock as before.

A bound is tagged with the lap - the stream's start and each rewind
count one - its position was read in, and the mix drops it once the
decoder has rewound: after a loop the window has let the old lap's
pages go, and a bound worked out from the old position would admit
reads of them. The locked version applied such a bound whenever the
loop fell between the feeder's two calls. A bound set with no
position read first is nobody's lap and stands across rewinds.

A position is at most one mix pass old, a few kilobytes against the
megabytes of lookahead both feeders keep.

audio_mixer_loop_test case 3f covers the bound being applied by the
mix, a stale one dropped and a fresh one taken; it fails on the
locked code. audio_mixer_voice_threads_test runs a feeder thread
against a looping stream voice under ThreadSanitizer.
2026-10-04 12:02:15 +02:00
LibretroAdmin 1bdfd580b4 xinput: rumble is written off the frontend's thread
XInputSetState() goes down to the controller's driver and returns when
the driver has taken it, and both XInput joypad drivers called it from
the frame loop each time a core changed a motor's strength. XInput is
the default joypad driver on Windows.

The frontend's thread now notes both motors' strengths in one atomic a
controller and returns; a thread of the driver's own makes the call.
Nothing is queued: the writer reads what is wanted when it gets to it,
so a controller slow to answer is given the latest strength and not
each in turn. There is no lock; the writer is woken by an event.

The writer is one header, xinput_rumble_writer.h, used by
xinput_hybrid_joypad.c (what desktop Windows builds) and by
xinput_joypad.c. DirectInput controllers under the hybrid driver are
written as before. Xbox and UWP builds are unchanged.

samples/input/xinput_rumble includes the real driver with an
XInputSetState that takes 60 ms, under Wine, and is in CI; it found
the writer reading its starting point after a strength had already
been asked for, which is fixed. Not run on a real controller.
2026-10-04 12:01:02 +02:00
libretroadmin 0754faf84d image_texture: GX tiling is asked for, not applied to every decode
Decoders emit linear images on every platform. image_texture_tile_gx()
rewrites one in place as GX RGBA8 tiles, through the same four-row band,
and the overlay loader calls it when gfx_surface's requirements name
GFX_SURFACE_PIXFMT_GX_RGBA8 - the gx driver points the GPU at overlay
pixels where they lie, and nothing else on the GameCube or Wii wants
tiles. RGUI's wallpaper and thumbnails, the thumbnail downscale and the
image viewer core read rows, and now get rows. A pack loaded for the gx
driver keeps no APNG stream either: nothing can show its frames, so the
file is not read a second time and its bytes do not sit in MEM1.

samples/formats/image_tile_gx checks the helper against the layout's
definition and against the band conversion byte for byte, at every size
up to 70x44, and runs in the formats samples workflow.
2026-10-04 11:59:42 +02:00
libretroadmin 4d6815ebb2 gfx_surface: owned payloads, format-keyed uploads, sized from the format
An external submit hands the surface a payload with its pixels, and the
completion frees it before release() runs - and still frees it when the
owner freed the surface meanwhile and release() never comes. A still
thumbnail's decoded image goes over that way, so a reset or an animation
replacing the still's surface while its upload is in flight no longer
strands the image; gfx_surface_free_adopt() is the same mechanism with
free(). Submissions carry their format: gfx_surface_new() takes one
gfx_surface_pixfmt bit and sizes slots and copies from it, the external
submit takes a gfx_surface_src_t, and the texture key is layout plus
channel order, so an in-place update never writes one layout into a
texture made for another. 2101010 reaches the driver as 2101010, which
restores 10-bit overlays and stills; a format the driver does not sample
is refused rather than mislabelled, a slot is narrowed in place where it
can be, and owners narrow their own images. submit_pixels() refuses a
slotless surface. FP16 is no longer advertised from a scRGB framebuffer
flag, since no texture path or menu composite carries it yet; the sizes
and keys here already follow the format, so adding it is a driver change.

image_texture_narrow_10bit() writes the order supports_rgba names, so the
descriptor stays true and no caller swizzles after it in a second pass.
threaded_video_test gains an external surface lane for the cancelled
payload and the refused formats.
2026-10-04 11:59:38 +02:00
LibretroAdmin 81e061794e udev: rumble is written off the frontend's thread
The udev joypad driver uploaded and played rumble effects from the
frame loop. The upload (EVIOCSFF) is answered by whatever is behind
the pad: for a virtual pad made through uinput that is another
program, and the frame waited for it.

The frontend's thread now notes the strength wanted and returns, and a
thread of the driver's own writes it. Nothing is queued: the writer
reads what is wanted when it gets to it, so a pad slow to answer is
given the latest strength and not each one in turn. The gain goes the
same way.

There is no lock. A strength is an atomic a motor. A pad's descriptor
reaches the writer as a dup left in a slot, taken out by exchange by
whichever thread is to close it; a pad that goes has its generation
raised, at which the writer lets go of what it holds. The writer is
woken through a pipe. Without threads the same code runs where it
always did.

samples/input/udev_rumble includes the real driver and gives it a pad
that takes 60 ms to answer; it runs under ASan, UBSan and TSan in CI.
Not run on a real controller.
2026-10-04 11:45:02 +02:00
LibretroAdmin f539ccd3d6 task_queue: flags and progress are atomics, off property_lock
task_get_flags(), task_set_flags(), task_get_progress() and
task_set_progress() each took the process-wide property_lock, so every
handler polling for cancellation and every progress update queued
behind every other task's, and behind the progress report formatting a
title under the same lock. The queue itself took it again to cancel,
to settle a task and to test for a main-thread or detachable one.

retro_task_t::flags and ::progress are retro_atomic_int_t now. Reads
are an acquire load, progress a release store, and flag updates an
atomic fetch-or / fetch-and, so concurrent updates of different bits
cannot lose one another. Only a backend whose read-modify-write is not
atomic (RETRO_ATOMIC_LOCK_FREE undefined) still takes property_lock,
and only around the flag update.

Title and error stay under property_lock: their buffers are freed and
replaced, which an atomic pointer would not make safe. The snapshot
and the progress report read the flags before the strings, so a task
seen finished is seen with the error it set before finishing.

Code that touched the two fields on a task already pushed goes through
the accessors: the progress callbacks, the cloud sync handler, the
emscripten HTTP handler and the cheevos badge tasks. Setting them on a
task not yet pushed is unchanged.

task_queue_snapshot_test covers both properties: bits flipped from two
threads with a third held, and a finished task always snapshotting
with its error. retro_task_threaded_gather drops from 8 lock
acquisitions to 6 and the budget reference moves with it.
2026-10-04 11:20:19 +02:00
LibretroAdmin d307939703 input: Aim From Analog Stick, for a port's lightgun or pointer
A lightgun or a pointer was aimed by a mouse or by touch, so there
were as many of them as there were mice (issue #13425).

New setting in a port's controls, "Aim From Analog Stick"
(input_playerN_aim_stick: OFF by default, Left Analog, Right Analog).
With it on, where the port's stick is held is where its lightgun and
its pointer point, the stick's centre being the screen's; the gun is
never off the screen, and its trigger is the pointer's press. The
gun's buttons are bound as they always were.

The stick is read as it comes, with no deadzone, and is still given to
the core as a stick. Lightguns read as movements (the old X and Y) and
the mouse are not covered. The frontend harness gains a lane for it.
2026-10-04 11:19:26 +02:00
LibretroAdmin adf3cc063d rh264, rh265: buffer and job pools without a lock
The recycled plane/motion blocks (rh264) and the finished slice jobs
and unescape buffers (both decoders) sat on lists under one lock per
decoder family, taken by the submitter for every slice and by every
pool thread for every job it finished.

They are slot arrays now. A slot holds a pointer or nothing and
changes hands in one atomic swap, so whoever swaps a pointer out owns
it: no list to walk, nothing read from an object another thread may
still free, and no ABA since a slot is never compared against a stale
pointer. A block of the wrong length goes back to a slot, its length
being readable only once it is owned.

A finished job keeps its unescape buffer and is pooled with it, so the
two pools are one and the submitter makes one draw per slice instead
of two; a slice whose buffer is not a context's reuses the pooled one
instead of allocating.

Backends with no pointer atomics (RETRO_ATOMIC_HAS_PTR undefined) keep
the slots under the lock.
2026-10-04 11:04:07 +02:00
LibretroAdmin 5a178e9a2b rvp9: tile-column join waits on an eventcount
The caller waiting for its frame's tile jobs took a process-wide lock
and condition, and the last job to finish took the same lock to wake
it. Both sides use a retro_eventcount now, as rh264 and rh265 do for
their rows: a finishing job notifies without a lock unless a thread is
parked, and the caller re-checks the count inside the wait window.
2026-10-04 11:04:04 +02:00
LibretroAdmin 21bb4305b5 input: overlay buttons alone do not make the menu combination
The combination of RetroPad buttons that opens the menu (L3 + R3,
Start + Select and the rest) was looked for in everything held,
whatever held it. A thumb resting on two overlay buttons opened the
menu by accident, on an overlay that has a menu button of its own
(issue #19472).

Where the system's input is collected, the buttons something other
than the overlay holds are now noted: a controller, a key, a command,
a network pad. Before the combination is looked for, buttons the
overlay alone holds are taken out, if the overlay shown has a menu
button. An overlay with no menu button is left as it was, the
combination being its only way in, and what the core is given is
unchanged.

The quit combination is not touched. The frontend harness gains a
lane with a small in-memory overlay.
2026-10-04 11:03:45 +02:00
LibretroAdmin 80ac643318 retroarch: a load without content names its saves after the core
Each load clears the content basename and the save file, save state,
replay and cheat names before parsing its arguments, so a load with
content sets them from that content and a load without names them after
the core, instead of keeping the previous content's.

The content_load harness checks a contentless load after a content load
keeps none of the content's names, and gives every load a save state
directory in its config rather than a startup -S path.
2026-10-04 10:56:26 +02:00
LibretroAdmin 8cece8e07e input: the pointer is captured for named reasons
Mouse grab was one flag, INP_FLAG_GRAB_MOUSE_STATE, that about ten
places toggled or rewrote, each after looking at it, so what it ended
up as went by the order they ran in. Leaving exclusive fullscreen let
go of a grab game focus still wanted; game focus going off in a window
let go of one the user had asked for; a driver restart dropped the
user's grab by way of game focus being applied again.

Each of them now holds or releases its own reason: the user's toggle,
Automatic Mouse Grab, game focus, exclusive fullscreen, and any
fullscreen (which hides the cursor and grabs nothing). The pointer is
captured while any is held, and the flag is what that comes to,
written in one place. The reasons are kept above the drivers, so a
driver that restarts has them applied to it again and the special
cases that preserved the flag are gone.

What changes for a user: a grab toggled on outlasts game focus, a
fullscreen round trip and a driver restart; game focus keeps its grab
through a fullscreen round trip; game focus going off in borderless
fullscreen lets go. The toggle, pressed while captured, still lets go
of everything.

The frontend harness gains a lane, with an input driver that notes
what it is told, and the Wine smoke test a scenario with Automatic
Mouse Grab.
2026-10-04 10:47:07 +02:00
LibretroAdmin 0f6ba6828b runloop: a core is opened to describe itself once per file
libretro_get_system_info() keeps what it asks of a core - its system
info, no-game support and content info overrides - keyed by the core
file's size and modification time, in core_probe.cache next to the
core info cache. A core already asked is answered from the record, so a
load no longer opens the new core at push just to read these; a new or
changed file is opened as before, and a core that declares subsystems
always is.

The menu's read-ahead of content decides by the core being loaded,
from those same declarations, instead of the override list of the core
still running. The content_load harness checks a known core is not
reopened at push, a changed file is, the record is read back from disk,
and the read-ahead follows the new core's overrides in both directions.
2026-10-04 10:07:45 +02:00
LibretroAdmin 8b35b4efa4 menu: Network RetroPad's users behind one entry
Settings > Network listed a switch a user, eight or more of them, with
Network RetroPad on. One entry, "Network RetroPad Users", now opens
the list of them, and shows the users chosen ("1, 3") or "None".
The switches and their config keys are unchanged.
2026-10-04 10:07:16 +02:00
LibretroAdmin 7334c35088 menu: Input Information lists the Network RetroPads
A user's Network RetroPad is a controller no joypad driver knows of,
so Information > Input Information did not show it. Each one listening
is now listed with its port and the address of the device last heard
from it, or that none has sent yet.
2026-10-04 10:07:12 +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 2c724ff91c GameCube/Wii: build os/gekko on every devkitPPC
newlib.c serves all three generations of devkitPPC's newlib system
hooks, picked at compile time: r49's lock, condition and thread hooks;
newlib 4.3's split plain and recursive lock hooks over int-sized locks;
and the older single set that takes the lock kind at init, which also
leaves __getreent to the system.  On the int-sized locks a plain lock is
a gk_mutex_t word and a recursive one a small handle into a table of
gk_rmutex_t.

rthreads and rcheevos run on os/gekko's own threads and mutexes rather
than newlib's pthreads, which only exist from r49.
2026-10-04 09:50:15 +02:00
LibretroAdmin cfb1204314 input: Network RetroPad takes all that was sent each poll
A sender sends one message for each control that changes, and the
poll read one message a user. Controls changed together arrived a poll
apart, and a moving stick, two messages a frame, fell further behind
the longer it moved. The poll now takes everything queued, up to a
bound.

Also:

- Whether a read found nothing was judged by errno, which Windows does
  not set for sockets, so there a held control could be let go at the
  next poll. libretro-common's non-blocking receive is used instead.
- A message of the wrong size cleared what was held. It is ignored.
- A user with no socket ended the whole poll, skipping the users after
  it and a replay's poll. It is skipped alone.
- With ports assigned on first press, a button of a network pad counts
  as a press of its user's.

The frontend harness gains a lane that sends real messages over
loopback; it fails on the old receive code.
2026-10-04 09:43:38 +02:00
LibretroAdmin 49aa2b0886 menu: OK on Keyboard Index opens a list
OK stepped to the next keyboard, as right does. It now opens a list of
'All' and the keyboards there are, as Mouse Index and Device Index do,
and the port is pinned to the one picked.
2026-10-04 09:30:53 +02:00
LibretroAdmin d877bf40f8 menu: sublabels for four Network settings
Digital Input Sharing, Analog Input Sharing, Network Commands and
Network RetroPad had none. Each now says what it does; the last two
also say that nothing is asked of the sender.
2026-10-04 09:30:49 +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 b3358ccba8 winraw: Background Keyboard Input
New setting "Background Keyboard Input" (input_keyboard_background,
off by default, Windows raw input only), beside "Background Controller
Input" (issue #18528).

With it on, the keyboard is registered with Windows as a sink, so its
keys arrive while another application is active, and running content
reads them: RetroPad binds, keys bound to a stick, and the keyboard
itself. Hotkeys do not answer to such keys, nothing does while the
menu is up, and they are not typed into the menu. Keys held when the
window stops being the active one are no longer let go, since their
releases now arrive.

Off, nothing changes. The registration follows the setting as it is
changed, with no restart.

samples/input/winraw_keyboards covers the driver side under Wine. That
Windows delivers the keys in the background has not been run.
2026-10-04 08:47:05 +02:00
LibretroAdmin 249e091a92 runahead: the second instance does not take the running core's callbacks
Registrations the frontend keeps a pointer for - the hardware context,
the keyboard, frame time, audio and audio buffer status callbacks,
disk control, the core options display callback, memory maps,
controller and subsystem info, input descriptors and the netpacket
interface - are answered for run-ahead's second instance without being
stored. The running core has made them all already; taking the copy's
handed its callbacks and memory to the copy and left them dangling once
the copy was closed. A hardware context is refused outright, so a
hardware core runs the single-instance method.

The content_load harness creates a second instance of a hardware core
and checks the hardware callbacks and controller description stay the
running core's and the next close of the running core goes through.
2026-10-04 08:32:40 +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 7d935ad609 menu: netplay's Request Device switches behind one entry
Settings > Network listed sixteen "Request Device N" switches in a
row. They are now behind one entry, "Request Devices", which shows the
chosen player slots ("1, 3", or "None") and opens the list of
switches.

The settings themselves and their config keys
(netplay_request_device_pN) are unchanged.
2026-10-04 07:53:30 +02:00
LibretroAdmin c68ec7556f runloop: the display rate helper is built with the menu
runloop_display_hz is only called from HAVE_MENU code and is compiled
under the same guard, so --disable-menu builds have no unused function.
2026-10-04 07:51:57 +02:00
LibretroAdmin 1e7f31890f runahead: the second instance's copy is opened by its copy task
The task that copies the core for run-ahead's second instance opens the
copy as well, on the worker under the threaded queue, and hands the
handle over with the path; the main thread takes it as it is and opens
the path itself only when the task could not. A copy discarded before
it is used is closed before it is deleted.

The runahead copy harness checks the task opens the copy once and hands
it over, a refused open still publishes the copy, and stale copies are
closed before they are deleted; its temp path check is portable to
Windows. The content_load harness creates a second instance of a
software core on the threaded queue against the real loader.
2026-10-04 07:51:53 +02:00
LibretroAdmin e35bbfed29 content load: the core is opened once
The core stage takes the system info, the library strings and no-game
support from the handle it opens, instead of opening and closing the
core just before to read them. Driver and menu init skip the same probe
while a core is running, so its retro_set_environment runs once per
load.

The content_load harness counts retro_set_environment calls on the
running core and checks the library strings and the no-game flag come
from that open.
2026-10-04 07:51:50 +02:00
LibretroAdmin 9ddbcb0f9b input: the first-press notification names all that was assigned
A port's controller and its keys go to the core port together,
whichever of them was pressed. The notification named only the one
that was pressed, so after "Keyboard assigned to core port 1" the
port's controller was on that core port too with nothing having said
so.

It now names what the port has: "<controller> and keyboard assigned to
core port N" when the port has a controller connected and keys bound,
the controller alone when it has no keys, the keyboard alone when it
has no controller.
2026-10-04 07:35:29 +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 497551183c input: first-press assignment and replays, netplay, remap files
Three loose ends of "Assign Ports on First Button Press":

- A replay has to see the ports it was recorded with. The policy is
  off when a recording or a playback is about to start, as it already
  was under netplay. If either begins while a user is still unmapped,
  the next poll gives up: each such user gets its own port and the
  policy stays off until content starts again.

- A remap file saved after a press had given a user its port stored
  that port, which then decided it from the next start on. Such a port
  is left out of the file. One set in the menu is the user's choice and
  is still saved.

- The setting is looked at when content starts. Turning it off in the
  middle could leave users without a port and no way to get one; a
  change now has no effect until content starts again, as its
  sublabel says.

The frontend harness's first-press lane covers all three.
2026-10-04 06:46:30 +02:00
LibretroAdmin ae0c6903d2 menu: XInput for Xbox Controllers is listed on Windows only
configure defines HAVE_WINRAWINPUT on every system, so the setting
added in 9ec817d52 showed in Settings > Input on Linux too, where it
does nothing. Its row, menu entry and sublabel are now under _WIN32 as
well.
2026-10-04 06:31: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 ecf8e20524 input: a controller port can be mapped to no core port
Mapped Port (Quick Menu > Controls > Port N Controls) gains 'None': the
port's controller reaches no core port, so the core does not see it.
Hotkeys and the menu are unaffected.

The value is MAX_USERS. The port map already treated it as unmapped;
the places that index per-port arrays by the mapped port now skip it,
and a remap file may store it. With 'None' chosen the list has no
Device Type or bind entries, since there is no core port for them to
refer to.

This is the first step of first-press port assignment (PR #18331),
where ports start unmapped.

The frontend harness's core-view lane gains a scenario: a pad on a
port mapped to 'None' reaches the core with no button held.
2026-10-04 05:47:29 +02:00
LibretroAdmin f8c536d352 config: a mapped port from a remap file stays in range
input_remap_port_pN was stored as read. It indexes per-port arrays of
MAX_USERS entries (the port's libretro device, its input descriptors,
its analog requests), so a malformed remap file could make those reads
go out of bounds. A value outside [0, MAX_USERS) is now put back to the
user's own port, as the button remaps in the same file already are
checked.

samples/tasks/input_remap covers it.
2026-10-04 04:31:15 +02:00
LibretroAdmin 9ec817d523 winraw_joypad: optionally read Xbox controllers through XInput
Raw input reads an Xbox-compatible pad as a plain HID device whose two
triggers share one axis, so they cannot be told apart when both are
held.

A new setting, "XInput for Xbox Controllers" (input_winraw_xinput_enable,
off by default, Windows only), hands those pads to XInput instead: the
raw input device is skipped and XInput's pad is listed with the XInput
driver's name, buttons, hat and axes, so the existing XInput autoconfig
profiles apply and each trigger has its own axis. Every other controller
stays with raw input. XInput is asked which pads exist when the driver
starts and when raw input reports a device change, not on every poll.

It is off by default because it changes button and axis numbers for
those pads; bindings made by hand under raw input would need redoing.

Rumble for such a pad goes through the same thread as before, with the
XInput pad already known.

Tested under Wine with XInput faked (samples/input/winraw_joypad_rumble)
and end to end with the setting on. Not run with a pad.
2026-10-04 04:24:15 +02:00
LibretroAdmin 71318ef1ec Fix build warnings on old GCC, uClibc and old wayland-scanner
Statics rely on implicit zero initialisation instead of {0}, and local
aggregates whose first member is an array get the matching braces, so
GCC 4.x -Wmissing-braces stays quiet. Locals the compiler cannot prove
written are initialised (rh265 keep[], rflac block header, query.c
lengths), loops older VRP misreads are reshaped, the unused SDL2
geometry forward declaration is gone, and task_queue no longer names a
variable main.

rzstd probes BMI2 with __get_cpuid_max/__cpuid_count, which every
cpuid.h the branch accepts provides. Hole punching needs uClibc-ng 1.0
with its Linux-specific API on uClibc. The sinc resampler's pop_options
does not warn on GCC 4.x MIPS. generate_wayland_protos.sh strips the
event type and deprecated-since attributes for scanners whose DTD
predates them, which ignore them anyway.
2026-10-04 04:23:50 +02:00
LibretroAdmin 4852dff33a GitLab CI: GameCube/Wii keep building on libogc
The buildbot's GameCube and Wii jobs stay on the devkitPro image and
devkitPro's libogc they have used, asked for with HAVE_LIBOGC=1, until
the cores build against os/gekko too.
2026-10-04 04:03:08 +02:00
LibretroAdmin 51cf64850b samples: input tests build on Windows and Linux
sony_pad_output_test and sony_pad_motion_test link features_cpu.c,
which on Windows needs retro_sleep_us() from rtime.c; without it they
built on Linux only. Both now build and pass with gcc and with
mingw-w64.

udev_devices_test tests a Linux-only driver; on other systems its
targets say so and succeed.
2026-10-04 04:01:03 +02:00
LibretroAdmin c3b8124bf6 GitLab CI: GameCube/Wii build in devkitPro's devkitppc image
The GameCube and Wii jobs need devkitPPC r49 or later; they build in
devkitPro's own devkitppc image, without the libogc flags the build no
longer reads.
2026-10-04 03:56:22 +02:00
LibretroAdmin b6fb3b7eca intl: two compiled Python files are not part of the tree
c49ff4ce1 committed intl/__pycache__/json2h.cpython-310.pyc and
json2h.cpython-312.pyc along with the change it was for. They are
what Python leaves behind when a script imports intl/json2h.py -
several of the checks under tools/ do - so they showed as modified
after any such run, and one more careless "git add" would have
committed them again. .gitignore already names them; they are taken
out of the tree.
2026-10-04 03:54:49 +02:00
LibretroAdmin d67a7f68d8 samples: the udev input driver's keyboards, mice and pins, run on a desk in memory
The udev driver lists the keyboards and mice on the desk, numbers the
real mice first, tells the frontend what each is known by, and gives
a port the one keyboard or mouse it was given. Four changes built
that, and each went in "compiled only": the driver needs input
devices under /dev/input, which a build machine does not have.

It does not need them to be real. What the driver knows of a device
- its name and ids, which physical device it is part of, whether it
has a keyboard's keys or sends pointer events, what its USB interface
says - is a structure in memory, filled in when a device is opened.
samples/input/udev_devices includes udev_input.c whole, stands in
for the frontend, and hands the driver a desk of such structures: a
keyboard that is three event devices (its keys, its media keys, a
pointer its macros can move), a mouse that is two (the mouse, and
keys its buttons send), a power button, and a second keyboard. The
listing, the numbering and the reading are then the real code's.

Held:

- two keyboards are listed, by name and ids, oldest first; the
  mouse's keys and the power button are no keyboard;
- the mouse is mouse 1; the keyboard's pointer part comes after it
  and is marked as not a mouse;
- each keyboard and mouse is told to the frontend by its ids, in the
  order of their numbers;
- a port given a keyboard reads a key held on any part of it and not
  one held on the other keyboard; a port given none reads every
  keyboard; a port whose own is away while another has its own reads
  no keyboard;
- a port reads the mouse the frontend says it reads, and none where
  it says none;
- with the mouse unplugged the lists are made again.

Nothing in the driver had to change: it passed as it was. In CI,
under the address and undefined-behaviour sanitizers.

What this does not cover is the step before: how the driver fills
those structures in from a real device's sysfs entries and key
capabilities. That still needs a Linux machine running the udev
input driver.
2026-10-04 03:54:44 +02:00
libretroadmin 4c6d8311b9 runloop, video thread: the menu at Content Rate under display pacing has one clock
With Threaded Video Display Pacing on, the menu at Menu Frame Rate
'Content Rate' over a stopped core is held to the content's period by
the display pacing hold alone. The hold keeps the content's period
there rather than the display's, and the frame limiter's timer stands
aside for it, as it already does at 'Display Rate'. The two, at nearly
the same period and never an exact multiple of each other, beat, and a
frame landed a vblank early or late every so often.

The menu pacing table's Content Rate + Display row reads Display alone,
and the threaded-video harness measures the menu over a paused core at
both rates.
2026-10-04 03:39:04 +02:00
LibretroAdmin 73ad914486 GameCube/Wii: build with devkitPPC r49 or later
os/gekko implements newlib's lock, condition and thread hooks, which
devkitPPC has had since r49; an older toolchain now stops at a plain
#error saying so.  The Wii and GameCube workflows build in devkitPro's
own devkitppc image, which carries a current devkitPPC.
2026-10-04 03:21:08 +02:00
LibretroAdmin 1c9249d57c winraw_joypad: a DualShock 4's or a DualSense's motion sensors
Under the raw input joypad driver a core that asked for a pad's
accelerometer or gyroscope got nothing: the driver had no sensors.
Raw input reads these pads by their reports' HID description, which
covers the sticks and buttons; the sensors are in the same report, in
a part the description calls vendor data.

input/common/sony_pad_motion.h reads them from where each pad's USB
report has them and turns them into what a core is given: the
accelerometer in g, the gyroscope in radians a second, on the axes
and with the signs the SDL joypad driver gives the same pads'
sensors, so that a core sees the same pad the same way under either
driver. The driver gives them through the joypad driver's sensor
calls, which the frontend asks before the input driver's.

- Read on demand. A pad's sensors are read out of its reports only
  while a core has asked for one of them: twelve bytes at the end of
  the parse the report gets anyway, and nothing for a pad nothing
  was asked of. This is the plan's per-port motion demand, for this
  driver.

- What a core asked is the port's, and kept: a pad unplugged and
  plugged in again, or the driver restarted because another pad was,
  goes on giving the core what it asked for.

- Over USB only. Over Bluetooth both pads send a short report with
  no sensors in it until they are switched to their full one, which
  has no HID description and would take the sticks and buttons away
  from a driver that reads by the description. A request is taken,
  and no value is given.

- The scale is the pads' nominal one. Each pad carries its own
  calibration, which is not read: values are right to a few percent,
  and a gyroscope at rest may read a little off zero.

No thread and no lock: the reports are parsed and the values asked
for on the polling thread.

samples/input/sony_pad_motion holds where the values are read from
and what they come out as; samples/input/winraw_joypad_motion runs
the real driver under Wine with reports handed to its parser: who
has sensors, nothing before a report has had them, only what was
asked, none read for a pad nothing was asked of, a short report
changing nothing, and a pad coming back still giving what was asked.
Both in CI.

No pad was read. The report layouts and scales are the ones the
Linux kernel's and SDL's drivers for these pads use, set down from
memory of them: whether a core's motion follows the pad is for a pad
to say.
2026-10-04 03:14:33 +02:00
LibretroAdmin 36e1248f1e winraw_joypad: an Xbox pad rumbles, through XInput
Under the raw input joypad driver an Xbox pad - any pad Windows
drives through XInput - is read as a plain HID device and had no
rumble: raw input has no way to write to it, and unlike a Sony pad
it takes no output report. XInput has the way, XInputSetState(), and
its own numbering of the pads, one to four, with nothing that says
which raw input device is which of its pads.

The rumble thread, which writes Sony pads' reports, now sets these
pads' motors as well. A pad whose device path has Windows' "IG_"
mark is handed to the thread as an XInput pad, with nothing opened.
The thread loads XInput when such a pad is first to rumble, and
works out then which of XInput's pads it is:

- with one such pad here and one of XInput's not yet taken, they are
  the same pad, whichever number XInput gave it;

- with more, it is the pad of XInput's whose buttons are held as this
  pad's are. The driver publishes each such pad's first ten buttons
  as it parses a report - XInput's ten, in raw input's order - and
  the thread compares. With no button held there is nothing to tell
  them by, and the pad is not rumbled until there is: better no
  rumble than another player's. What was wanted stays wanted and is
  tried again at the next change of strength;

- the last pad left is known by there being no other.

A pad XInput no longer answers for is looked for again. Unplugging a
pad stills it and lets XInput's pad go; stopping the driver stills
the rest and unloads XInput.

As with the Sony pads, nothing is under a lock: the strength wanted
and the buttons are retro_atomic ints, the pad is handed over through
the same inbox, and everything XInput - the library, which of its
pads is taken, the calls - is the thread's own. The poll's part is
one atomic store a report, for XInput pads alone; XInput is never
called from it.

How the pad is read does not change: its buttons and axes, and so
its bindings and profiles, are raw input's as before.

samples/input/winraw_joypad_rumble, under Wine with XInput faked:
one Xbox pad and one pad of XInput's - its third - are the same pad,
and XInput is loaded only when it first rumbles; of two, none is
rumbled while nothing tells them apart, one is as soon as a button is
held on it, and the other is then the one left; an unplugged pad and
a stopped driver leave no motor running. Five runs, all the same.

Not run with a pad.
2026-10-04 03:05:18 +02:00
LibretroAdmin 3f2ac53c41 config: the list of string settings overflowed, and RetroArch died at startup
afd41a82d added input_playerN_mouse_device, sixteen string settings, after
input_playerN_keyboard_device had added sixteen. configuration.c
builds its string settings into an array allocated for
SETTINGS_ARRAY_COUNT_MAX of them, 96 + MAX_USERS = 112; with the two
it is given 120. Adding a setting wrote to the next place in the
array whether there was one or not, so the last eight were written
past the end of the allocation - and Windows' heap ended the process
(0xC0000374) at the next allocation, in config_load_file(), on every
start.

- The list is sized for what it holds: 96 + 3 * MAX_USERS.

- No list can be written past its end again. GENERAL_SETTING, which
  every SETTING_* line of every list goes through, stores a setting
  only where the list has room; one that does not fit is left out and
  reported as an error naming the setting and the size to raise.
  Each populate_settings_*() says what its list holds.

- tools/settings_capacity_check.py finds it before anything runs:
  it preprocesses configuration.c as tools/settings_check.py does,
  for the same build profiles, counts the settings each list is given
  - times the count of a loop over the users - and compares that
  with the size the list is allocated with. With the size master had
  it fails ("array list: given 120 settings, holds 112"); with this
  it passes. It runs in the settings samples job.

The existing settings gates pass with the changed macro: the table
matches its reference, and the unity compile is clean.
2026-10-04 02:51:08 +02:00
LibretroAdmin b3e49e7545 overlap check: two rows of one array are not the same buffer
memcpy(rows[1], rows[0], n) copies between rows of a two-dimensional
array, which cannot overlap; the check now tells that from a copy
between addresses inside one buffer (&a[i], a + 1), which it still
flags.  This clears samples/input/device_pins, and the selftest has the
case.
2026-10-04 02:30:24 +02:00
LibretroAdmin c860493032 GameCube/Wii: build on os/gekko by default, drop the in-tree libogc
Makefile.wii, Makefile.wii.salamander and Makefile.ngc build on
RetroArch's own system layer, os/gekko.  HAVE_LIBOGC=1 still builds the
_libogc drivers, always against devkitPro's libogc and libfat; the copy
of libogc under wii/libogc, the build of it (Makefile.libogc) and the
libsysbase bridge it needed are gone, and with them EXTERNAL_LIBOGC and
GX_PTHREAD_LEGACY.  The Wii and GameCube workflows build the default.
2026-10-04 02:22:41 +02:00
LibretroAdmin afd41a82d8 input: a port keeps its mouse by what the mouse is
The same as for keyboards (see "a port keeps its keyboard by what
the keyboard is"): Mouse Index is a place in a list that changes
when a mouse is plugged in or out, and the place was all that was
kept. What is kept now for a port whose mouse was chosen is the
mouse's identity (input_playerN_mouse_device) - its USB ids, or its
name where it has none - and the number follows from it each time
the input driver makes its list of mice.

Two things differ from keyboards, and come from what Mouse Index is.
Every port has a number from the start, its own, so that the first
port reads the first mouse; and there is no "every mouse as one".
So:

- a port is pinned only when its mouse is chosen by hand - stepped
  to in the menu or picked from the list - never from a number it
  merely has. A configuration from before this stays as it was until
  Mouse Index is next set. Resetting the setting removes the pin;

- a port whose pinned mouse is not there reads no mouse while another
  port has its own, and the mouse its number names otherwise. Mouse
  Index shows it by name, "046d:c08b (not connected)".

Windows raw input and udev say what each of their mice is known by
and ask the frontend which mouse a port reads
(input_mouse_port_index()), in place of the setting. Under every
other input driver nothing changes: no mouse has an identity, no
port is pinned, the setting is read as before.

Also: the pins header built an identity for the second of two
devices of one model in a way gcc warned could truncate; it cannot,
and is written so that the compiler sees it.

No lock and no thread in any of it: the list, the pins and the
lookups are all on the thread that polls.

samples/input/device_pins has the mouse cases, in CI.
samples/input/winraw_keyboards holds, through the real raw input
driver under Wine, what the driver says its mice are known by - the
two parts of one mouse told apart, in the order of their numbers -
and what a pinned port then reads. The end-to-end run has a port
pinned to a mouse that is away.

The udev side is compiled only. Not run with two real mice.
2026-10-04 02:22:31 +02:00
libretroadmin d9dfddab4b mcp: SCREENSHOT answers with the picture as well as its path
The MCP SCREENSHOT tool answers with the screenshot itself as image
content, PNG up to 4 MiB, followed by the file's path as text, so a
client on another machine, or without access to the screenshot
directory, can see what is on the screen. Past 4 MiB, or for another
format, it answers with the path alone as before.

The screenshot task reads the PNG it has just written back on its own
thread and base64-encodes it there, as the encoder streams rows into
the file and keeps no copy; the main thread only hands it on. A command
interface takes it through the new optional reply_image_to, which only
the MCP server sets; every other interface is sent the text.
2026-10-04 02:22:08 +02:00
LibretroAdmin e9764d115e winraw_joypad: rumble passes to its thread through retro_atomic, with no lock
The rumble thread and the driver shared each pad's device handle
under a CRITICAL_SECTION, and the strength wanted and the quit flag
through Win32's Interlocked calls. The lock was there for one thing:
that the driver, closing a pad's device when the pad is unplugged,
does not close it under a write the thread is making. Holding it
across the write meant that unplugging a pad could wait a whole
write - up to the quarter second one is given.

None of it needs a lock, and the tree has retro_atomic for the rest:

- the strength wanted and the quit flag are retro_atomic ints;

- an open device belongs to one side at a time. The driver opens it
  and hands it over through the pad's inbox, a pointer swapped in
  with retro_atomic_exchange_ptr(); from then on it is the thread's,
  which writes to it and is the one to close it. A pad that goes is
  told the same way: "close" is swapped in, and the thread stills
  the motors and closes the device when it gets to it. A device
  handed over and never taken - the pad went again first - comes
  back out of the swap and is closed by the driver.

So unplugging a pad waits for nothing, and no handle is closed under
a write. The thread itself and the event that wakes it stay Win32's:
the file is Windows raw input's, and an auto-reset event is the wait
that needs no lock around it.

samples/input/winraw_joypad_rumble, under Wine: as before, and an
unplugged pad is stilled and closed by the thread shortly after; a
pad plugged in and out fifty times faster than the thread takes its
device leaves none open; stopping the driver stills and closes the
rest. Six runs, all the same.

Not run with a pad.
2026-10-04 02:03:26 +02:00
LibretroAdmin 2e149e1eaf gekko: USB mice on the Wii
Boot protocol HID mice on the Wii's USB ports are found with the
keyboards (rvl/hidboot.c, which reads both) and add up their motion,
wheel and five buttons for gk_mouse_read().  In the gx input driver all
USB mice together are mouse 0 while there are any; without one, the
Wii Remote's pointer is the mouse as before.  The HID pad driver leaves
mice awake as it does keyboards.
2026-10-04 01:59:51 +02:00
libretroadmin b11affe6c6 CI: the embedded MCP server is tested over the loopback
samples/network/mcp_server builds network/mcp_server.c as it ships, in
front of a stub command table, and drives it from a client on the same
thread: the protocol handshake, the tool list and its read-only and
destructive hints, tool calls answered at once and on a later frame
(a path naming ERROR is no error), the JSON-RPC errors, the HTTP gate -
method, path, a foreign Origin, the bearer token it will not start
without - a body past the limit, a request arriving in pieces, and a
waiting request answered when the server goes.

The network clients workflow runs it under ASan, UBSan and LeakSanitizer,
and built with mingw-w64 under Wine.
2026-10-04 01:57:56 +02:00
LibretroAdmin 20dc26992e samples: win32_window_proc_test links again
71a610e92a gave win32_common.c its first RARCH_DBG call ("Menu bar
built").  The window procedure test links the shipping win32_common.c
against its own stand-ins for the loggers, and those had RARCH_LOG,
RARCH_WARN and RARCH_ERR only, so the link failed on an undefined
RARCH_DBG - and, the step being in the middle of the samples-gfx job,
the 22 steps after it were skipped.

The stand-ins gain RARCH_DBG.  The test passes, 2700 trace lines as
recorded, so the menu bar change did not alter what the window
procedure does.
2026-10-04 01:55:17 +02:00
LibretroAdmin ee036de86d input: a port keeps its keyboard by what the keyboard is
A port is given a keyboard by its number in the list of keyboards
(Keyboard Index), and the number was all that was kept. It is a place
in a list that changes: with two keyboards, each given to a port,
unplugging the first makes the second "keyboard 1" - the port that
had "2" then names a keyboard that is not there and falls back to
all of them, and the other port reads the wrong one.

What is kept for a port now is the keyboard's identity, a pin
(input_playerN_keyboard_device), and the number follows from it each
time the list of keyboards is made (input/common/
input_device_pins.h):

- the identity is what the input driver knows a keyboard by that
  stays the same from day to day: its USB vendor and product id, or
  its name where it has none. Windows raw input and udev tell it for
  each keyboard they list. The second of two of one model is
  "<identity>#2", which holds while they are listed in the same
  order;

- a port whose pinned keyboard is listed reads that keyboard,
  whatever number it has now; Keyboard Index shows the new number;

- a port whose pinned keyboard is not there reads no keyboard while
  another port has its own - its keys must not come from that one -
  and every keyboard while none has: a single player who pinned a
  keyboard that is now unplugged still has keys. Keyboard Index shows
  it by name, "046d:c31c (not connected)", not as a number that is
  some other keyboard's now, and the log says what the port reads;

- a number with no pin - a configuration from before this - is taken
  at its word and the port pinned to the keyboard it names. Changing
  Keyboard Index in the menu pins the port to the keyboard chosen;
  "All" removes the pin.

The drivers no longer read the setting: they ask the frontend what a
port reads (input_keyboard_port_choice()), which is all keyboards,
one of them, or none.

Mouse Index is still a number; that is the same change again and
comes next.

samples/input/device_pins runs the rule on its own, in CI: two
pinned ports each read their own keyboard in whatever order the
keyboards are listed; one unplugged, the other is still its own
port's and the port left without reads none; a single port whose
keyboard is away reads every keyboard; two of one model are two pins.
samples/input/winraw_keyboards holds the same through the real raw
input driver under Wine, and the end-to-end run has a port pinned to
a keyboard that is away: the core still gets its keys and the log
says why.

The udev side is compiled only. Not run with two real keyboards.
2026-10-04 01:54:52 +02:00
libretroadmin c2b7cf0f9c mcp: a reply is an error only when it opens with its command and ERROR
An MCP tool result answered after its command returned is marked an
error when the reply opens with a command name and then ERROR, or with
-1, rather than when " ERROR" appears anywhere in it: SCREENSHOT now
answers with the screenshot's path, which a game named with ERROR in it
would carry, and that screenshot was reported as having failed.

command_screenshot() says why it takes the screenshot threaded: its
callback has to run after the reply's destination is taken.
2026-10-04 01:50:51 +02:00
Rob Loach 007ebda0e6 mcp: Add callback to tooling to fix return state 2026-10-04 01:53:36 +02:00
LibretroAdmin 668fe39a53 AGENTS.md: regression tests must build on Windows and Linux
Harnesses that only build on one platform leave the other untested; keep test code portable and check it with both a Linux toolchain and mingw-w64.
2026-10-04 01:49:34 +02:00
LibretroAdmin a6a68e8a87 Wii: the network logger, and logger macros that take any format
make -f Makefile.wii HAVE_LOGGER=1 builds network/net_logger.c in and
sends every log line as a UDP datagram to PC_DEVELOPMENT_IP_ADDRESS,
on the native system layer as on libogc (which has no libbba to link
on the Wii).

The HAVE_LOGGER log macros format their arguments behind the prefix
in one call instead of pasting the prefix onto the format string, so
a format held in a variable compiles; samples/frontend/logger_macros
checks that under all four logger configurations.
2026-10-04 01:41:11 +02:00
LibretroAdmin 0dfd6c089a menu: Input Information lists the keyboard and mouse of a driver that names none
Input Information lists the controllers on the ports and, since the
keyboard and mouse work, the keyboards and mice the input driver
names. Two drivers name them: Windows raw input and udev. Under
every other - Wayland, X11, SDL, DirectInput, Cocoa - a machine with
no controller plugged in got "No Entries to Display": a laptop on
Wayland, with a keyboard and a touchpad under the user's hands, was
shown as having no input at all.

Those drivers are handed keys and pointer motion by a window system
that has already merged every keyboard into one and every mouse into
one; there is nothing to tell apart, but there is a keyboard. Where
the running input driver names no keyboard and can read one, the
list now has

  Keyboard: All Keyboards (wayland)

and the same for mice, with the driver's name, and under either the
line that says why: the driver reads them as one device and cannot
tell them apart. A driver that names its devices is listed as
before.

Looked at under X11 with the x input driver, which names its mice
and no keyboards: the keyboard is listed this way and the mouse by
its name. Wayland itself was not run.
2026-10-04 01:41:00 +02:00
LibretroAdmin b8e84c232e sony_pad_output: the Bluetooth CRC is libretro-common's CRC-32
The header had its own bit-by-bit CRC-32, so as to stand alone. It
is the same CRC-32 encoding_crc32() computes - the report test checks
the result against a table-free run of the polynomial, and still
passes - so the header calls that, over the unsent 0xA2 and then the
report.

The tests that compile the raw input joypad driver, and the report
test, link encoding_crc32.c and what it needs.
2026-10-04 01:05:01 +02:00
LibretroAdmin a8539b6ba1 winraw_joypad: a DualShock 4 or a DualSense rumbles
Raw input reads a controller and has no way to write to one, and the
raw input joypad driver had no rumble at all: a core's rumble went
nowhere under it, whatever the pad.

For the two pads there is a rumble report for, it has it now. A
DualShock 4 or a DualSense (or Edge) rumbles on an output report in
its own format, different for each pad and for USB and Bluetooth;
input/common/sony_pad_output.h builds it, asking for the motors and
nothing else - the light bar, the player lights, the triggers and
the audio are left as they are. Windows lets a controller's HID
device be opened for writing, so the driver opens it when such a pad
arrives.

The write is not made on the frontend's thread. It is a transfer on
the controller's link - about a millisecond over USB, many over
Bluetooth - and the frame is not to wait for it. set_rumble() notes
the strength wanted and wakes a thread of the driver's own, which
writes what is wanted now, not every step on the way to it. A write
is given up after a quarter of a second, so that a pad gone out of
range cannot hold up its own unplugging. Unplugging a rumbling pad,
and stopping the driver, still the motors first.

This is the second part of the plan's WP-09: output writes go to the
provider's thread and never from the iterate.

Other controllers have no rumble under this driver still; an Xbox
pad's goes through XInput, which is another piece of work.

samples/input/sony_pad_output holds each report's id, length and
layout, that only the rumble flags are set, and the Bluetooth CRC.
samples/input/winraw_joypad_rumble runs the real driver under Wine
with files standing in for the controllers: the two Sony pads are
opened and another make is not; set_rumble() writes nothing itself
and the thread writes the pad's own report at the length Windows
wants; a run of changes ends with the last written; unplugging and
stopping still the motors. Both in CI.

No pad was written to. The report layouts are the ones the Linux
kernel's and SDL's drivers for these pads use, set down from memory
of them and checked only against themselves: whether a pad rumbles
is for a pad to say.
2026-10-04 00:58:00 +02:00
LibretroAdmin ddb2053e20 input: one rule for which devices are keyboards and which are mice
The Windows raw input driver and the udev driver each say which of
the devices a system reports are the keyboards on the desk. udev did
it through input/common/input_keyboard_devices.h; raw input had its
own copy, kept until its results had been seen on real hardware.
They have been: one keyboard listed for a keyboard in two parts, a
mouse that sends keys and a vendor's virtual device left out.

The header now has the mice as well - which pointers are mice on the
desk, and which are a keyboard that can send pointer events
(input_kbdev_mice()) - and both drivers go by it for both:

- raw input tells it what Windows says of each device and takes its
  answer, where it had the two rules written out. What it lists and
  how it numbers things is unchanged; samples/input/winraw_keyboards
  passes as it was.

- udev gets the mouse side it did not have. Every pointer was a
  mouse there, in the order the devices are in, so that a keyboard
  that can send pointer events could be mouse 1 - the one the first
  port reads unless told otherwise. The mice on the desk are numbered
  first now, in the order they had, and the rest after them, still
  to be chosen by number and left out of Input Information and of
  what Mouse Index offers. A pointer's USB interface is read for
  "boot mouse" as a keyboard's is for "boot keyboard".

  As with raw input, this changes which number a mouse has under
  udev wherever something that is not a mouse was numbered before
  one.

samples/input/keyboard_devices runs the mouse rule on its own: a
keyboard's pointer part and a pointer with nothing behind it are no
mice; a mouse, a mouse on a receiver with a keyboard, a Bluetooth
keyboard's trackpoint and a laptop's touchpad are.

The udev driver is compiled only.
2026-10-04 00:46:39 +02:00
LibretroAdmin 60b0e0318f vulkan: the window is looked at right after a mode change, not a frame later
6d323f62d has a resize that is known of before a frame is drawn done
before it. On Windows the in-place fullscreen toggle still had every
frame after a toggle refused: a log of eleven toggles has eleven
"QueuePresent failed ... destroying swapchain".

The driver did not know. It hears of a new window size from the
window check in vulkan_alive(), and with threaded video the video
thread runs that after it has drawn a frame, not before. The toggle
restyles the window on that thread between two frames; the next
thing the thread does is draw, and only after that frame - drawn at
the old size, its present refused, the swapchain thrown away and made
again - does it look at the window.

vulkan_set_video_mode() now looks at the window itself, as soon as
the context has applied the mode. If the size changed, the resize is
waiting when the next frame starts, and that frame is drawn at the
new size into a swapchain made for it.

Not run where it matters. Here, on X11, no path reaches this with a
window whose size changed - the fullscreen toggle restarts the driver
there - and samples/gfx/vulkan_resize passes as it did.
RETROARCH_VULKAN_RESIZE_LATE=1 still leaves the resize to the end of
the frame.
2026-10-04 00:38:28 +02:00
LibretroAdmin 71a610e92a win32: the menu bar is kept while the window has none, not built again
The menu bar belongs to the windowed state. It was destroyed whenever
the window went fullscreen, or was taken back from the last video
driver, and a new one built - made from its template, every item put
into the user's language - each time the window was windowed again:
once for every return from fullscreen and for every driver restart.
It is the same menu each time.

It is now taken off the window and kept, and put back when the window
is windowed again; a window that still has its menu keeps it. A new
one is built only when there is none kept. One kept is dropped when
the language changes (win32_menubar_rebuild()), so that the next
windowed window gets one in the new language.

Under Wine, retroarch.exe with OpenGL and the menu bar on, through
four fullscreen toggles and a driver restart: the window had its menu
bar each time it was windowed and none while fullscreen, and the menu
was built once where it used to be built four times.
samples/input/windows_wine_smoke holds the count in its two OpenGL
scenarios.

Not run on Windows; that the kept menu still answers a click was not
tried.
2026-10-04 00:32:35 +02:00
LibretroAdmin 6d323f62d8 vulkan: a resize known of before a frame is drawn is done before it
The swapchain was made again for a new size once a frame, after the
frame had been presented. A resize the driver is told of between two
frames - vulkan_alive() sets the flag - is known of before the second
is drawn, so that frame was drawn at the old size into a swapchain
image the window no longer matched, its present came back "out of
date", the swapchain was thrown away and made again for that, and
only then was it made for the new size.

The same step now also runs first in vulkan_frame(), before
anything is drawn: the frame after such a resize is drawn at the new
size into the new swapchain, and presented. The run after the
present stays for a change that comes up while a frame is being
drawn. Nothing about the step itself changes; it is the code that
was there, in a function (vulkan_apply_pending_resize()).
RETROARCH_VULKAN_RESIZE_LATE=1 leaves it to the end of the frame
alone, as it was.

Where this is expected to show is the in-place fullscreen toggle on
Windows: the window is restyled by RetroArch itself between two
frames, and the frame after it was the one drawn at the old size and
refused.

It could not be shown here. On X11 under a window manager-less
virtual display the window is resized by the server while a frame is
already on its way, and that frame's present is refused before the
driver has heard of anything; that one is not this patch's to
remove, and samples/gfx/vulkan_resize counts the same four refused
presents with and without it. What that run does hold is that
nothing else changed: same pictures, same swapchains made, no
validation errors.
2026-10-04 00:32:31 +02:00
LibretroAdmin 91e65ba6ae CI: the Vulkan resize job installs the Vulkan loader it links against
The job's configure stopped at 'Forced to build with library
-lvulkan, but cannot locate': --enable-vulkan makes configure look
for the loader, and the runner image has none. libvulkan-dev is
added to what the job installs.
2026-10-04 00:32:28 +02:00
LibretroAdmin 9a51388f64 vulkan: the shader chain is kept when the swapchain only changed size
The second half of what the driver did on every swapchain change.
The driver now keeps what is not made from the swapchain's size; the
shader chain still rebuilt itself whole each time it was told of a
new swapchain: every pass's descriptor pool, set layout, pipeline
layout and pipeline destroyed, its framebuffer made again, its
SPIR-V reflected again and its pipeline compiled again, then the
uniform buffer and the history and feedback buffers gone over. With
a preset of a dozen passes that was a dozen pipeline compiles on
every resize of the window and every fullscreen toggle.

What a pass builds is made from the render pass it draws to and its
format, and from the number of sync indices. Nothing is made from
the size: viewport and scissor are dynamic state, and a pass whose
framebuffer follows the viewport already brings it to size as it
draws, each frame, from the viewport it is handed then
(slang_pass_build_commands()).

So slang_chain_update_swapchain_info() now only notes the new
viewport when the render pass, the format and the index count are
what the chain was built for and every pass is built. Anything else
- a format change, HDR going on or off, a pass not built yet - is
the rebuild it always was. RETROARCH_VULKAN_REBUILD_ALL=1 rebuilds
every time, here as in the driver.

samples/gfx/vulkan_resize runs a preset of its own as well: a first
pass at half the viewport's size that draws squares eight of its own
pixels wide, so that the picture says what size its framebuffer is,
and a second that reads the first's last frame as feedback. Over
four resizes the passes were built once, at load, and the chain kept
four times, where with the switch they were built five times; the
picture after each resize is pixel for pixel the full rebuild's;
the validation layers report nothing on either.

Not run: Windows; HDR; a preset from the shader repositories.
2026-10-04 00:15:16 +02:00
LibretroAdmin a65a23144a gekko: debug output to a USB Gecko
A USB Gecko in memory card slot B or A is found at boot and gets every
line of debug output, stdout and stderr (crash reports included), for a
terminal on the computer it is plugged into.  Output is dropped while
nothing reads it, and the SD adapter code leaves its slot alone.

run-dolphin.sh GECKO=1 puts Dolphin's USB Gecko in slot B and wants
the test's verdict to come through it as well.
2026-10-04 00:03:56 +02:00
LibretroAdmin 3030fcfc73 vulkan: a swapchain that only changed size has only its framebuffers rebuilt
Whenever the swapchain was replaced the driver destroyed and built
again everything it had built on it: every pipeline and the pipeline
layout, the render passes, the samplers and the default texture, a
command pool, a descriptor pool and a vertex and a uniform buffer for
each image, and the textures the core's frames are uploaded into.

Of all that, only the views of the swapchain's images and the
framebuffers on them are made from its size. The render passes and
the pipelines are made from its format, the per-image pools and
buffers from how many images it has, the HDR pipelines and buffers
from whether HDR is in play, and the samplers and textures from none
of it. And a swapchain is replaced for its size far more often than
for anything else: every resize of the window, every fullscreen
toggle that is done in place, and once at every start of the driver.

vulkan_check_swapchain() now notes what the resources were built for
- format, image count, the context's HDR flags - and when those are
what they were it remakes the views and framebuffers and keeps the
rest. When one of them has changed everything is rebuilt as before.
RETROARCH_VULKAN_REBUILD_ALL=1 rebuilds everything every time.

Keeping the frame textures also puts right something that could be
seen: with the menu open over a paused core, a resize showed
whatever the new textures happened to hold behind the menu, since
the core's last frame went with the old ones. It is kept.

The shader chain is still told of every swapchain change and still
rebuilds itself whole; that is its own step.

samples/gfx/vulkan_resize, and a CI workflow for it: retroarch built
with Vulkan and X11, on Mesa's software Vulkan driver, resized four
times with the menu closed and open. As it is, all four size changes
rebuilt the framebuffers only; with the switch, all four rebuilt
everything; the picture after a resize is pixel for pixel the same
on both; the Vulkan validation layers report nothing on either.
Threaded video on, and by hand off.

Not run: Windows, where the in-place fullscreen toggle takes this
path; HDR, which this box cannot do; a shader preset.
2026-10-03 23:54:16 +02:00
LibretroAdmin c4805c7133 gekko: Guitar Hero guitars on the Wii Remote
The guitar is read on the Classic Controller's layout: frets, strum
and +/- on its buttons, the stick as the left stick and the whammy bar,
scaled to the travel it has shown, pushing the right stick right.  It
autoconfigures as "Guitar Hero Guitar", frets bound as on guitars whose
face buttons carry the fret colours.
2026-10-03 23:46:10 +02:00
LibretroAdmin dafa1f8374 menu: Mouse Index offers the mice, not everything with a number
Mouse Index offered all sixteen indexes whatever was on them: on a
desk with one mouse, the mouse, the keyboard - which can send pointer
events and so has a number - and fourteen lines of "N/A".

Where the input driver lists its mice, Mouse Index now offers the
mice on the desk: not an index with no mouse, and not one the driver
says to leave out of Input Information - a keyboard's pointer part,
a mouse a program made. That goes for the drop-down and for stepping
left and right, which pass over the others. The index chosen now is
always in the drop-down, whatever it is, so that a setting from
before is not shown as nothing.

Under an input driver that names no mice every index is offered, as
it always was. The other indexes are still there for a config file
to name.

input_config_mouse_offered() is the one place that says which.

Not looked at on screen.
2026-10-03 23:33:31 +02:00
LibretroAdmin d082f986ce gekko: start IOS 58 when the loader's IOS has no USBv5
USB (pads, keyboards, storage) needs IOS 57 or later.  A program
started on an older IOS launches IOS 58 through ES before main(), waits
for the new IOS to take requests and starts its IPC state over; MEM2
shrinks to what the new IOS leaves.
2026-10-03 23:32:53 +02:00
LibretroAdmin d6f2d54a67 gekko: load only the sections, not the ELF headers
Programs are linked into two segments, code and data, that start where
the image does, so a loader no longer writes the ELF headers over the
low-memory globals (memory sizes, bus clock, IOS version) in front of
it.
2026-10-03 23:32:49 +02:00
LibretroAdmin e1a634e9cc winraw: the mouse comes first among the mice, and a program's keyboard is not listed
Two things from Input Information on a desk with one keyboard and
one mouse, both under Windows raw input.

It listed two keyboards: "Corsair composite virtual input device"
and the keyboard. The first is a device a program made, for its
macros and remaps; nothing on the desk is behind it. Windows hangs
such devices off its ROOT (or SWD) enumerator, where no hardware is,
and that is now read with the rest of what Windows says of a device:
a keyboard, or what it hangs off, on one of those is not listed, as
the terminal server's is not. Its keys still feed every port left on
All.

It listed the keyboard as "Mouse 1" and the mouse as "Mouse 2". The
keyboard can send pointer events, which makes it one of raw input's
mice, and it is the older device. That was not only the list: the
number is the Mouse Index, and the first port reads mouse 1 unless
told otherwise - on that desk, the keyboard.

The mice on the desk now come first, in the order they had. After
them, still to be chosen by number and left out of Input
Information, come:

- the pointer part of a device that has a USB boot keyboard and no
  boot mouse: a keyboard that can send pointer events. Where it is
  not known what a part is, it is taken for a mouse, and a keyboard
  and a mouse on one receiver are both what they are;
- a mouse a program made, and the terminal server's.

This changes which number a mouse has under raw input wherever
something that is not a mouse was listed before one: a port that was
given a Mouse Index to get past such a device has to be given it
again. A port left alone now gets a mouse.

samples/input/winraw_keyboards: its desk has the program's keyboard
and mouse hanging off ROOT, and a keyboard with a pointer part. Two
keyboards are listed, not three; of six raw input mice the three on
the desk are first in the order they had, and the keyboard's pointer
part, the program's mouse and the terminal server's follow and are
marked to leave out. What is read of a device - made by a program,
boot mouse - is checked on its own.

Not run on Windows. udev orders and lists its mice as it did.
2026-10-03 23:06:24 +02:00
LibretroAdmin f4ac9970fb gekko: USB keyboards on the Wii
Boot protocol HID keyboards on the Wii's USB ports type into the menu
and cores and drive the keyboard binds.  os/gekko finds them among the
HID devices IOS lists, sets the boot protocol and turns their reports
into connect, key and disconnect events; the gx input driver turns
those into key events and key state, US layout for characters.  The
HID pad driver leaves keyboards awake.  The USB HID usage key map is
shared with the Wii U as rarch_key_map_hid.
2026-10-03 22:58:07 +02:00
LibretroAdmin b36b192cac gekko: the Wii's L2 with its fetch and castout enhancements
At boot, with everything written back and the L2 invalidated, HID4
turns on 64-byte fetches, the second castout buffer, miss under miss
and castouts finished before a flash invalidate, as the 750CL manual
configures the L2.  They cannot be turned off without a reset, so the
loader's stub loses its own HID4 write before RetroArch returns to it.
2026-10-03 22:58:03 +02:00
libretroadmin c0d86d1fb7 gfx_thumbnail_preview: the APNG fixture is sized past the window on every host
mkfixtures.py writes the lossless APNG itself, fourteen 1280x720 frames
of seeded noise, rather than through ffmpeg: ffmpeg 6.1's APNG encoder
keeps only the rectangles that change between frames, and from 30 s of
testsrc2 made 33 frames and 3.4 MiB, inside the window the feeder keeps,
which failed the windowed test's check that the file is past it. Noise
does not compress, so the file is ~37 MiB on every host, and the frames
are the same each time.
2026-10-03 22:57:47 +02:00
LibretroAdmin c367465118 input: connected mice are listed in Information > Input Information
After the controllers and the keyboards: "Mouse 2: <name>", with the
mouse's USB ids under it where the driver knows them.

The number is the mouse's Mouse Index - the number a port is given
the mouse by - and nothing about how mice are numbered changes. One
mouse on the desk is often more than one of the input driver's mice,
so where the driver says which belong together they are one line
with all of their numbers, "Mouse 2, 3: <name>", and an index with
nothing on the desk behind it is left out.

The frontend keeps that beside the mouse names
(input_config_set_mouse_device()), and clears names and all when an
input driver starts: a mouse name used to stay from whichever driver
set it last.

Windows raw input says of each mouse which device it is part of, by
the same reading of what Windows says that the keyboards are listed
by, its ids, and that the terminal server's mouse is none. A mouse
that gives no product name used to be shown by its device path, or
as "<name not found>"; it goes by what Windows calls it. That name
is also what Mouse Index shows. The list is replaced whole when it
is made again, so a mouse that has gone is not left in it.

udev says the same from what it already knows of each pointer; the
"N/A" it puts on the indexes with no mouse is not a mouse to list.
X11 lists its master pointers as it names them. The other input
drivers read one mouse and name none.

samples/input/winraw_keyboards holds what the raw input driver tells
the frontend: on the desk of the report with a second part to the
mouse and the terminal server's mouse added, the five mice keep
their numbers, two of them are one mouse, the terminal server's is
one to leave out, and the nameless one has a name.

Not run: real Windows; udev, compiled only; the menu screen was not
looked at.
2026-10-03 22:54:37 +02:00
LibretroAdmin c4693cf76d udev: the keyboards listed are the ones on the desk
The same as was just put right for Windows raw input, on Linux. The
udev input driver took every device that has a key for a keyboard:
the power button, the video bus, a headset's volume keys, a mouse
with keys of its own - and one keyboard for as many as it has event
nodes. All of them were in Input Information, and a port given "a
keyboard" got one node of one.

What a keyboard on the desk is now has one place,
input/common/input_keyboard_devices.h: devices that are parts of one
physical device are one keyboard; it is listed if one of its parts is
a keyboard in its own right; not if it is also a pointer and none of
its parts is a USB boot keyboard - a mouse whose buttons send keys;
and where that is not known, it is listed.

The udev driver tells it, of each device, from what the kernel says:

- which device it is part of: the physical path, the same for every
  event node of a USB device but for the last "/inputN"; over
  Bluetooth the device's own address;
- whether it is a keyboard in its own right: it has every key from
  Escape to D, the test udev's own ID_INPUT_KEYBOARD goes by;
- whether its USB interface is a boot keyboard's, from sysfs;
- its USB ids, which the menu now shows under the keyboard.

A listed keyboard goes by the name of the first of its parts that is
a keyboard, and a port given it (Keyboard Index) reads a key from
any of its parts, its media keys included. Every device with keys
still feeds the one key state, listed or not.

samples/input/keyboard_devices, in CI, runs the rule on its own: a
desk as Linux reports one - a keyboard in three parts, a mouse whose
buttons send keys, a headset, the power button and the video bus -
lists one keyboard; two keyboards of one model are two; a keyboard
with a pointer built in is one, as is one of which nothing is known.

The udev driver itself is compiled only: this machine has no input
devices to run it on. The Windows driver keeps its own copy of the
rule for now.
2026-10-03 22:32:44 +02:00
libretroadmin 795bf2114f font_driver: a new font has no block bound
The font allocated by font_driver_init_first() starts with block_bound
false, rather than whatever the allocation held, which the first text
drawn before a block was bound read as the decision to count a draw.
2026-10-03 22:27:36 +02:00
libretroadmin 0ae22773a8 gfx_display: still images go through a surface, and under threaded video are not waited for
The menus' theme icons and wallpapers, the widgets' icons and the white
texture are loaded through a static gfx_surface. Under threaded video, on
the main thread, the upload is queued rather than a round trip to the
video thread, and the handle is written into the caller's slot when it
completes; a theme reload no longer waits on the video thread once per
icon. Without the wrapper, off the main thread, and for compressed or
10-bit images, the load is the plain synchronous one, as before.

A slot reset or freed while its upload is in flight is cancelled first
by its owner - each menu driver, Ozone's sidebar nodes, the widgets -
and the completion then unloads the texture rather than write the slot.
A video driver torn down mid-upload completes each with no handle, so
nothing is written and nothing leaks.
2026-10-03 22:27:32 +02:00
LibretroAdmin 95afc2da13 libretro-common: dir_list: compare names without strcasecmp
The directory comparators fold case through a local helper instead of
strcasecmp/strncasecmp, which strict feature profiles do not declare on
older glibc (the webOS SDK fails with implicit declarations) and which
MSVC lacks. qstrcmp_plain shares the same loop, so ordering is unchanged.
2026-10-03 22:24:58 +02:00
LibretroAdmin 5bf4d73504 input: the keyboards listed are the ones on the desk, with their own details
Input Information on a desk with one keyboard and one mouse, under
Windows raw input, showed four keyboards - one with no name, the
mouse, and the keyboard twice - and under each of them the display
name, config name and USB ids of the controller on port 1.

The controller's details: the keyboard entries were given the
controller entry's label, whose sub-line is filled in from the port
number in the entry - 0. They have a label of their own now, and
their sub-line is the keyboard's own USB ids where the driver knows
them, and nothing where it does not.

The list itself: what raw input calls a keyboard is anything that can
send keys. The raw input driver now works out which device on the
desk each of those is part of - from the container id Windows gives
every part of one physical device, or failing that its USB ids - and
lists keyboards, not raw input's keyboards:

- the raw input keyboards that are parts of one device are one
  keyboard. A port given it (Keyboard Index) reads a key from any of
  its parts; before, the index named one part;
- the terminal server's keyboard, which every Windows has and nothing
  is behind, is not listed;
- a device that is also a mouse and none of whose keyboard parts is a
  USB boot keyboard is not listed: a mouse whose buttons can send
  keys. Where Windows does not say what a part is, the device is
  listed;
- a keyboard that gives no product name goes by what Windows calls
  the device it hangs off, not by "<name not found>".

Every raw input keyboard still feeds the one key state, listed or
not: a mouse's keys work for every port left on All. What was found
of each raw input keyboard, and why one was left out, is in the log.

samples/input/winraw_keyboards: what Windows says of a device is the
test's to say, and it sets up a desk like the one in the report - a
nameless virtual device that is also a mouse, a mouse that sends
keys, one keyboard in two parts, the terminal server's, and a
keyboard with a mouse built in. Three are listed, by the right names
and ids; a port given the two-part keyboard reads either part; the
mouse's keys reach only the ports on All. The reading of paths, ids,
containers and the boot interface is checked on its own.

Under Wine retroarch.exe makes the real queries: Wine answers the
path and nothing else, and its one keyboard is listed by its ids.
Not run on Windows: which of these rules the devices in the report
meet is for a log from that machine to say.
2026-10-03 22:23:10 +02:00
LibretroAdmin 3a11efc366 retroarch: no global_t
PAL60 is a PS3 setting, video_pal60_enable, in effect when the output
is 720x576; the state flags that stood in for it go.  The error jump
buffer, its message and the launch flags live in retroarch.c's own
state, with the flags among RARCH_FLAGS_*.  config_load() and the
menu's setting builders lose the global they no longer use.
2026-10-03 22:21:47 +02:00
LibretroAdmin 07207e294d input: a port can be given a keyboard of its own (Keyboard Index)
Two players on two keyboards could not have the same keys: every
keyboard was read as one. Each port now has a Keyboard Index beside
its Mouse Index (Settings > Input > Port N Controls,
input_playerN_keyboard_index):

  All (0, the default)  every keyboard as one, as before
  #N: <name>            the Nth keyboard the input driver lists - the
                        ones in Information > Input Information

A port given a keyboard reads its key binds - buttons, the bind mask,
analog and lightgun binds - and its RETRO_DEVICE_KEYBOARD from that
keyboard alone. What does not change, whatever a port is given:

- hotkeys answer to every keyboard;
- with the menu open every port reads every keyboard, so the menu can
  be worked from any of them;
- the keyboard callback and text input are every keyboard's;
- a port given a keyboard that is not there reads them all until it
  is.

The choice is not exclusive: two ports can be given the same
keyboard, and a port left on All also reads keyboards other ports
were given. It is stored as the driver's index, as Mouse Index is.

Windows raw input and udev, the two drivers that tell keyboards
apart, keep each keyboard's own keys beside the one state they all
feed. With that, two things about the one state are put right in
both:

- a key held on two keyboards went up when the first let go; it
  stays down, with no key-up, until the last does;
- a keyboard unplugged with a key held sent no key-up, and the key
  stayed down; what it held is let go, with a key-up, unless another
  keyboard holds it too.

Under raw input an injected key has no keyboard: ports left on All
read it, ports given a keyboard do not.

samples/input/winraw_keyboards holds all of the above for the real
raw input driver under Wine, with made-up keyboards. The first run
of it found the two-keyboards-one-key case.
samples/input/windows_wine_smoke has a scenario with the first port
given the keyboard Wine has: retroarch.exe reads the port's key bind
from that keyboard's own state and all else is as before.

Not run: real Windows with two keyboards; udev, which is compiled
only; the menu entry was not looked at on screen. One keyboard on
the desk can be several of raw input's keyboards, and the index
names one of them.
2026-10-03 21:32:08 +02:00
LibretroAdmin d84eee7b85 input: connected keyboards are listed in Information > Input Information
Input Information listed the controllers on each port. It now lists
the keyboards too, where the input driver can tell them apart:
"Keyboard 1: <name>", in the driver's order.

The frontend keeps the names (input_config_set_keyboard_display_name()
and friends, beside the mouse names), cleared whenever an input
driver starts; a driver that lists its keyboards sets them.

Windows raw input: the driver keeps the list of raw input keyboards,
oldest first as it orders its mice, and looks their names up in the
task that already does it for mice. The list is made again when a
key comes from a keyboard that is not in it - one plugged in since -
at the end of that poll and at most once a second, and at the poll
after Windows says devices came or went, which is how an unplugged
keyboard leaves it. An injected key, which has no device, never asks
for it. Every keyboard still feeds the one key state: this is which
there are, not yet a way to give a port one of them.

What raw input calls a keyboard is anything that can send keys. One
keyboard on the desk is often two or three of these, and a mouse or
headset with keys is one too; they are listed as raw input has them.

udev: it already indexed its keyboards; it names them now. While
there: its tables of mice and of keyboards hold MAX_INPUT_DEVICES
each and nothing kept a seventeenth out of them - what udev calls a
keyboard includes the power button and the like. They are bounded.

Other input drivers read one merged keyboard and list none.

samples/input/winraw_keyboards, in CI: the real raw input driver
under Wine with a device list the test controls - listed oldest
first and only keyboards; a new keyboard's first key lists it and is
not lost; injected keys and unlisted handles; once a second; an
unplugged keyboard gone at the poll after the device change.
samples/input/windows_wine_smoke now requires the raw input driver
to have named a keyboard: retroarch.exe under Wine logs
'Found keyboard #1: "Wine HID keyboard"'.

Not run: real Windows; udev, which is compiled only; and the menu
screen itself was not looked at.
2026-10-03 21:07:56 +02:00
LibretroAdmin 666d9901b3 egl: a hardware core's context is bound off the window surface on the main thread
Under threaded video an OpenGL hardware core's context lives on the
main thread while the driver's own holds the window surface on the
video thread.  egl_bind_hw_render bound the core's context to that
same window surface.  GLX and WGL allow one drawable on two threads;
EGL does not: eglMakeCurrent failed with EGL_BAD_ACCESS, the result
was not looked at, and the core's context_reset ran with no context.
Its first glGetString returned NULL - boom3 died in
R_CheckPortableExtensions - on every
EGL context, and by default on Android, where threaded video is on.

A thread that does not hold the window surface now gets the core's
context without it: surfaceless where the display has
EGL_KHR_surfaceless_context, else on a 1x1 pbuffer of its own (the
context's config, or one with the same buffers where that config has
no pbuffers).  A hardware core renders into the framebuffer objects
the driver hands it, so it needs no window.  The unthreaded bind, on
the thread that holds the window, is unchanged.

The Android context gains release_current, so the ring gives the
core's context up on the main thread at teardown instead of leaving
it current there; VideoCore's own teardown frees the pbuffer.

samples/gfx/egl_hw_core runs retroarch on the gl driver and the X11
EGL context under Xvfb with a hardware core, threaded and not, and
checks the core had a context and its frame reached the screen; a
preloaded shim hides parts of Mesa's EGL so the surfaceless path and
both pbuffer paths are each taken.  The three threaded runs fail
without this change.
2026-10-03 21:05:53 +02:00
LibretroAdmin 31849bf43e ui_win32: the raw input pump is compiled only where raw input is
b2968a37 gave the message pump a branch for when the raw input driver
reads by its poll: it takes everything below WM_INPUT and everything
above it. Where there is no raw input driver the branch was switched
off with a macro that expands to false, which still compiles it, and
the MSVC 2005 job builds for _WIN32_WINNT 0x0400 with an SDK that has
no WM_INPUT at that level:

  ui/drivers/ui_win32.c(160) : error C2065: 'WM_INPUT' : undeclared
  identifier

The branch is under the same condition as the driver now.

Nothing on GitHub could see this: mingw-w64 declares WM_INPUT whatever
_WIN32_WINNT is. The compile matrix has "win32-old" lanes that build
ui_win32.c and win32_common.c for 0x0400 behind a header that takes
those names away again, as that SDK does. The ui_win32 lane fails with
the same error before this change and passes after.

Not built with MSVC 2005 here. That was the only error cl reported for
the unit, and the other files that name WM_INPUT are not built below
0x0501.
2026-10-03 20:41:23 +02:00
LibretroAdmin f3bfc57ea1 input: the SDL3 joypad driver is read through the snapshot bridge
The last of the desktop joypad drivers that the frontend still read
one control at a time. As with SDL2, a read in this driver is a call
into SDL, and it had no get_buttons(): it has one now, a walk of the
pad's buttons through the call button() makes, and the bridge copies
the pad once a poll.

samples/input/joypad_bridge_checked has a test for it like SDL2's:
SDL is given two virtual joysticks, one it takes for a gamepad and
one it does not, the driver opens them as it opens any, and over 1500
random states what the bridge would read is what the driver's own
button(), axis() and state() say. It was run against SDL3 built from
source with virtual joysticks on. SDL3 is not packaged on the CI
image, so there the test is skipped, and says so; it is built where
pkg-config knows sdl3 or SDL3_CFLAGS and SDL3_LIBS are given.

Not run with a controller.
2026-10-03 20:40:46 +02:00
LibretroAdmin d3b2a314ef wayland: input events are dispatched by the input driver's poll, on a queue of their own
On Wayland the keyboard, the pointer and touch come over the same
connection as the surface, and everything was on its one default
event queue. That queue is dispatched from two places:
the input driver's poll, and the video side's check_window and
buffer swap. With threaded video those are two threads, so a key or
a pointer event was handled on whichever of them got to it - the
keymap and modifier state, the key and button state the poll reads,
the pointer position - written from the video thread under a
frontend reading them.

The seat is now put on an event queue of its own as soon as it is
bound, and the keyboard, pointer and touch objects made from it
follow it there; the relative pointer is made through a wrapper of
its manager so that it is on that queue from its first event.
Whoever reads the connection sorts what it reads into the queues,
and each side dispatches only its own: the video side the default
queue - surface, outputs, frame callbacks - as before, and the input
driver's poll the input queue (wayland_input_dispatch()). An input
event reaches its handler on the frontend's thread and nowhere else.
This is the pilot the plan's Phase 2 names.

At start-up the input queue gets a roundtrip of its own, so that the
seat's capabilities, and the keyboard and pointer with them, are
there before the window is set up, as they were.
RETROARCH_WAYLAND_INPUT_QUEUE=0 leaves the seat on the default
queue. webOS, whose Wayland library is an older one, is left as it
was.

Also: the relative pointer object was never destroyed, at teardown
or when the seat lost its pointer. It is now.

How many input events were handled, and how many of them on a thread
other than the frontend's, is logged when the connection closes.

samples/input/wayland_smoke, and a CI workflow for it: retroarch
built with Wayland and OpenGL, run under Weston on a virtual X
display, with the small core of windows_wine_smoke. Keys, pointer
motion, a fullscreen toggle, and - with pause-when-unfocused on -
the keyboard focus taken away and given back. With threaded video
and without: 137 input events, all handled on the frontend's thread;
the core saw its button and its key events throughout, and again
after the focus came back. With the queue turned off and threaded
video, 2 to 6 of the 137 were handled on the video thread.

Run under Weston with OpenGL only. Not run: the Vulkan context,
which shares this code; any other compositor; touch.
2026-10-03 20:20:37 +02:00
LibretroAdmin 7236ef8480 gfx: the GameCube, Wii and PS3 resolution is the fullscreen size
The resolution these consoles start in and switch to is
video_fullscreen_x/y, as everywhere else: 0x0 is the console's own
mode, a size it has no mode of falls back to it.  The display servers
map the size to their mode tables.  The PS2's modes, told apart by
more than their size, keep their table index in a setting of its own.
global_t no longer carries a resolution, and config_set_defaults()
only takes the settings it fills.
2026-10-03 20:20:30 +02:00
LibretroAdmin dca728cad8 input: linuxraw, SDL2 and the XInput-only driver are read through the snapshot bridge
Three more joypad drivers on the bridge that copies a controller out
of its driver once a poll. Each was held first to what the bridge
assumes - that get_buttons() says what button() says, that hats and
axes answer through button() and axis(), that state() is the common
loop - and two of the three were not there yet.

linuxraw: get_buttons() copied sixteen of the thirty-two buttons that
button() reads. Nothing but the bridge calls a joypad driver's
get_buttons(), so it showed nowhere; on the bridge, buttons 16 to 31
would have been dead. It copies all thirty-two.

sdl2: it had no get_buttons(). It has one, a walk of the pad's
buttons through the call button() makes. A read in this driver is a
call into SDL, so here the copy saves those as well: a core that read
sixteen buttons one by one made sixteen calls into SDL a frame.

xinput, the XInput-only driver (UWP, Xbox, builds without
DirectInput): it had no get_buttons() either, and has one, so it is
served like the hybrid driver of the same name. While there: for a
port with no controller the driver indexed its state table with -1 -
button(), axis() and state() all read the entry before the first one
before finding it not connected. They return before indexing now.

samples/input/joypad_bridge_checked has a test for each, with udev's
and DirectInput's: linuxraw's pad table filled by hand; the
XInput-only driver's state table filled by hand, under Wine; and for
SDL2, which keeps no table to fill, two virtual joysticks given to
SDL - one it takes for a game controller, one it does not - opened by
the driver as it opens any and set through SDL. Random pads, binds
and thresholds: what the bridge would read is what the driver's own
button(), axis() and state() say, in all five. With linuxraw's old
sixteen-button copy put back, its test reports 93704 disagreements.

Not run with a controller, and the XInput-only driver not on the
platforms that use it. sdl3 has no get_buttons() and is left reading
its driver directly.
2026-10-03 13:22:05 +02:00
LibretroAdmin 9f0dfecd88 input: a joypad driver restart asked for on another thread waits for the poll
On Windows a controller plugged in or pulled reaches the frontend
through the window procedure: a device notification arms a timer, and
the timer's message calls joypad_driver_reinit(). With threaded video
the window procedure runs on the video thread. So the joypad driver
was destroyed there - its pad tables freed, the registry started
over, every controller reported and configured again - while the
frontend's thread could be in input_driver_poll(), polling that
driver and reading those tables. Every Windows input driver does
this, raw input included: its own controllers come and go at the
poll, but the window's notification still restarted the joypad
driver from the video thread.

joypad_driver_reinit() asked for on a thread that is not the
frontend's now only notes it, and the next input_driver_poll() does
the restart, on the frontend's thread, before it polls or reads
anything. Asked for on the frontend's own thread - threaded video
off - it is done at once as before. Several requests between two
polls are one restart. The driver restarted is the one the setting
names, which is what both callers pass.

This is the plan's rule for Phase 1 - nothing reachable from a pump
callback reinitialises a device - for the one place that did.

samples/runloop/frontend_overhead, a new lane: asked for from a
second thread the driver is not touched there, and the next poll
destroys and restarts it once, on the frontend's thread; three
requests before a poll are one restart; on the frontend's thread it
is immediate.

samples/input/windows_wine_smoke: every scenario now has a controller
"plugged in" - the hotplug timer's message sent to RetroArch's
window. With threaded video the log must say the restart was left to
the poll, without it that it was not; the joypad driver starts once
more than before and the pad still works. All nine pass with
retroarch.exe built from this.

Not run with a real controller being plugged in.
2026-10-03 13:12:30 +02:00
LibretroAdmin c0135202d4 Makefile.test: the Wine targets make their prefix directory
f1110d33 put win32-waits-check in the unit test step, the first thing
there to run Wine. The step sets WINEPREFIX=/tmp/wineprefix and leaves
the first invocation to create it, and Wine does not create a prefix
under a directory that belongs to someone else:

  FAIL: eventcount, tier alert, hires 1
  wine: '/tmp' is not owned by you, refusing to create a configuration
  directory there

A directory that exists and is the user's own it takes. Both Wine
targets, win32-waits-check and win32-wine-check, make $WINEPREFIX
before the first tool runs when one is set.

Run here as a user who does not own /tmp, with a prefix that was not
there: an empty program is refused with that message before, and
win32-waits-check passes after, both hires passes.
2026-10-03 12:56:50 +02:00
LibretroAdmin a5c803c404 wgl: the OpenGL window is kept across a restart, and a fullscreen toggle restyles it
The Windows OpenGL context was the last of the common ones that
destroyed its window with every driver restart and restarted every
driver for a fullscreen toggle.

The window is now left up for the next OpenGL driver, as it is for
Vulkan and Direct3D, with the one condition OpenGL has. A window's
pixel format can be set once, and the format wanted depends on
whether HDR is on: the FP16 format when it is asked for and the
display is in HDR mode, the plain one otherwise. So a kept window is
taken only if the format it has is the one that would be chosen for
it now (win32_window_pixel_format_fits(), which follows
win32_setup_pixel_format() step for step); if not, it is destroyed
and a new one made, as before. win32_setup_pixel_format() no longer
sets a format the window already has. Desktop OpenGL only: an EGL
window goes as it did.

And a borderless fullscreen toggle restyles the window in place: the
context offers GFX_CTX_FLAGS_FULLSCREEN_IN_PLACE and its
set_video_mode calls win32_window_set_fullscreen() when there is a
window, as the Vulkan context's does. The GL context and everything
made in it stay; the driver sees a resize. All three OpenGL drivers
already pass set_video_mode through to the context.
RETROARCH_FULLSCREEN_IN_PLACE=0 and RETROARCH_WINDOW_KEEP=0 turn the
two off, as for the other drivers.

This one could be run: Wine has OpenGL, on Mesa's software
rasteriser. With retroarch.exe built from this, threaded video on and
off: the toggle changed the window from 960x720 to 1280x720 without a
GL context being made, and a screenshot showed the core's picture at
the new size; with the toggle made to restart instead, the window was
left up and taken back, a context was made on it with no pixel format
error, and the picture was drawn on it; closing content took the
window back the same way.

samples/input/windows_wine_smoke has two OpenGL scenarios for it, and
ONLY=<name> to run some of them; its CI build now includes OpenGL.
All nine pass.

Not run: real Windows, and HDR - the case where the format no longer
fits and the window has to be made again.
2026-10-03 12:51:04 +02:00
LibretroAdmin 04e3b47b0d win32_window_proc_test: stand-ins for what a kept window asks of the video thread
win32_common.c keeps the window for the next video driver now
(win32_window_keep, win32_window_release_kept) and calls
runloop_get_flags(), task_is_on_main_thread() and the three
video_thread_host_*() functions. The test links win32_common.c against
its own stubs, which had none of the five, so it stopped at the link:

  undefined reference to `runloop_get_flags'
  undefined reference to `task_is_on_main_thread'
  undefined reference to `video_thread_host_hold'
  undefined reference to `video_thread_host_is_held'
  undefined reference to `video_thread_host_stop'

The test drives the window procedure on the thread that made the
window: it is the main thread, and there is no video thread to hold or
stop.
2026-10-03 12:37:31 +02:00
LibretroAdmin e941a0d02a gx, gekko: gamma and the soft filter come with the frame
1d34b301 made the console gamma and soft filter settings, and f5d2119c
applied them on os/gekko. Both GX drivers apply them in the resize
their frame() runs, and read them there through config_get_ptr() - on
the video thread when video is threaded, while the main thread may be
writing them. The Wii and GameCube builds have threads.

  gfx/drivers/gekko_gfx.c: viewport_resize() reaches config_get_ptr()
  on the video thread; read the frame's snapshot instead
  gfx/drivers/gx_gfx_libogc.c: gx_resize() reaches config_get_ptr() on
  the video thread; read the frame's snapshot instead

video_frame_info_t carries both, beside the overscan correction it
already carries for GEKKO, and frame() hands them to the resize.

tools/video_thread_state_check.py passes. gx_gfx_libogc.c compiles in
the compile matrix's Wii and GameCube lanes. gekko_gfx.c has no lane
that compiles it off a console and was not compiled here.
2026-10-03 12:37:28 +02:00
libretroadmin f1110d3372 CI: the Windows wait tiers run under Wine
win32-waits-check builds the eventcount, asymmetric eventcount and
thread pool tests as Windows binaries and runs them under Wine on every
sleep tier of the eventcount and of the condition variable, with
bounded waits on the high resolution timer and with the kernel timeout
alone. No Linux build takes these paths. The libretro-common tests
workflow runs it, and it skips where MinGW or Wine is not installed.
2026-10-03 12:34:23 +02:00
libretroadmin d17ece2fb7 d3d10, d3d9: display pacing reads the vblank the last present went out on
D3D10 reports the present time display pacing aims at as D3D11 and D3D12
do: from the swapchain's frame statistics while they describe the most
recent present, otherwise the compositor's last vblank. Direct3D 9 has
no frame statistics, so both of its drivers report the compositor's last
vblank. Without a report the presenter laid the grid from its own
clock, which is no scanout at all.
2026-10-03 12:34:17 +02:00
LibretroAdmin c49ff4ce1d input: udev and DirectInput controllers are read through the snapshot bridge
The snapshot bridge copies a controller out of its driver once a
poll, and every read until the next poll - a core's state query, the
hotkeys, the menu - is served from the copy. It is switched on
driver by driver, when a driver has been checked for what the bridge
assumes: that get_buttons() says what button() says, that hats and
axes answer through button() and axis(), and that state() is the
common loop over the binds. Windows RawInput and XInput were on it.

udev (Linux) and dinput (the DirectInput-only Windows driver) join.

udev needed nothing: get_buttons() hands over the sixty-four bits
button() reads, and state() is the common loop as written. Its axes
past the sixteenth, which a copy does not hold, are read from the
driver as before.

dinput had no get_buttons(); it has one, over the button array
button() reads. Its state() compares an axis against the threshold
as integers, which comes to the same as the common loop's division
at every threshold.

RETROARCH_INPUT_SNAPSHOT=0 in the environment reads the drivers
directly; it was a Windows-only switch and is one everywhere now.

samples/input/joypad_bridge_checked, in CI: each driver's source is
included and its pad table filled by hand, four thousand random pads
each, and for random binds and thresholds what the bridge would read
is compared with the driver's own button(), axis() and state(). udev
on the host, DirectInput under Wine. Both agree throughout.

Not run with a controller: there is none on this box and Wine shows
none. The frontend-overhead harness and the Windows and X11 runs of
the frontend pass with the drivers on the bridge and no pad
attached.
2026-10-03 12:31:51 +02:00
LibretroAdmin 47b838a95c d3d12: the swap chain is released whenever the driver is freed
The driver did not release its swap chain: the Release() in
d3d12_gfx_free() was compiled out under a note that it crashed
eventually, and every freed driver left its swap chain behind.
c66a7ce8f made an exception for a window that is kept across the
restart, which cannot have a second swap chain while the first is
alive, and released it there - before the command queue, after the
back buffers, with the queue drained and the swap chain told it is
windowed.

That order has since been run on Windows through content loads,
closes and fullscreen toggles without trouble, so it is no longer an
exception: the swap chain is released that way on every free -
keeping turned off, a driver that takes over from another kind, and
the way out of the program included.

The builds without a window (UWP) are left as they were; the order
has not been run there.

Compiled for Windows. The path that is new is the one where the
window is not kept; it has not been run.
2026-10-03 12:31:47 +02:00
LibretroAdmin 513cb41123 d3d10, d3d11, d3d12: a borderless fullscreen toggle restyles the window
With the window kept across restarts, a fullscreen toggle on the
Direct3D drivers still restarted everything else. The toggle only
changes the window in place when the driver offers
GFX_CTX_FLAGS_FULLSCREEN_IN_PLACE and has a set_video_mode; the
Windows Vulkan context does, the Direct3D drivers had neither, so for
them it was CMD_EVENT_REINIT - every driver dropped and started
again. On d3d12 that is the device, the command queue, every
pipeline, the shader preset, the swap chain, the fonts and every
texture freed and made again; the audio driver stopped and started;
the menu's textures loaded again; and a hardware-rendered core told
its context was gone. What survived was the window, the video thread
and the input driver - the three that are kept. All for a change of
the window's style and size.

The three drivers now offer the flag and have a set_video_mode that
calls win32_window_set_fullscreen(), as the Vulkan context does. The
window is restyled where it stands, the new size reaches the driver
through check_window like any resize, and the swap chain's buffers
are resized as they are when the window's edge is dragged. Nothing
else is touched.

Exclusive fullscreen, which changes the display's mode, restarts the
drivers as before. RETROARCH_FULLSCREEN_IN_PLACE=0 in the environment
makes every toggle a restart again, on these drivers as on Vulkan;
the switch is one function now, win32_fullscreen_in_place(), and the
Vulkan context reads it there.

Compiled for Windows. Not run: Wine starts none of the three drivers.
The window side of it is the code Vulkan's toggle has been running
on; the drivers' side is their existing resize.
2026-10-03 12:17:12 +02:00
libretroadmin df15b0d290 rthreads: on Windows scond_wait_timeout ends on a high resolution timer
A timed wait on the Win32 condition variable is bounded in microseconds
rather than whole milliseconds, and sleeps on a high resolution waitable
timer, one per thread, together with the thread's event, on every sleep
tier, as the eventcount's bounded waits do; the waker signals that
event for a waiter that sleeps on one. Its kernel timeout ended on the
system timer's tick, 15.6 ms unless something in the process lowered
it. Unbounded waits are unchanged.

Where the timer cannot be had, before Windows 10 1803, the bound is in
whole milliseconds as before. The timer functions are resolved at run
time, so nothing new is linked; RTHREADS_SCOND_HIRES=0 keeps the kernel
timeout alone for comparison.
2026-10-03 12:13:39 +02:00
LibretroAdmin f5d2119c00 gekko: soft filter and video gamma on the Wii
The A/V encoder's composite video trap filter follows Soft Filter, and
Video Gamma sets its gamma curve, 0.1 to 3.0 in tenths, computed from
x^(1/gamma) over the video range.  host/run.sh checks the curves.
2026-10-03 12:09:08 +02:00
LibretroAdmin 1d34b301b8 settings: the console screen filters and gamma are settings
Video gamma, the soft filter and the Xbox flicker filter live in
settings_t, defined once in settings_def_video_console_screen.h, with
their config keys unchanged.  global_t keeps only the console's
resolution and PAL state.
2026-10-03 12:09:04 +02:00
LibretroAdmin c66a7ce8f4 d3d10, d3d12: the window is kept across a restart
The window is left up across a video driver restart, and taken back
by the driver that comes next, for Vulkan, GDI and Direct3D 11. These
are the other two Direct3D drivers anyone uses. They join by the same
means as d3d11: a tag on the window, so that only a driver of the
same kind takes it; win32_window_keep() in place of destroying it;
and, should a swap chain not be had on a window that was taken, a new
window and a second attempt (win32_window_remake()).

d3d10 needs no more than that. Its swap chain is released with its
device, and is of the kind - not the flip model - that a window may
have another of.

d3d12 needs its swap chain gone, and it never went: the Release() in
d3d12_gfx_free() has been compiled out for years under a note that it
crashes eventually. DXGI makes no second flip-model swap chain on a
window that still has one, so as it stood every restart on a kept
window would have fallen back to a new one. When the window is kept
the swap chain is now released, in the order that should be safe: the
queue is drained at the top of the function, the back buffers are let
go of just before, the swap chain is told it is windowed (releasing
one that is not is an error), and it is released while the queue it
was made on is still there. When the window is not kept -
RETROARCH_WINDOW_KEEP=0, or the program is closing - none of that
runs and the driver frees as it always did.

Why the old Release() crashed is not known, so whether this one is
safe is not known either until it has been run: none of it could be
here. Wine starts neither driver. Both compile for Windows. If d3d12
misbehaves, RETROARCH_WINDOW_KEEP=0 is the way back.
2026-10-03 12:08:12 +02:00
LibretroAdmin afff14a917 d3d12: a driver that could not start is freed without a crash
When Direct3D 12 cannot give the driver a device - a card or a
system without it - d3d12_gfx_init() gives up and frees what it has
through d3d12_gfx_free(), which begins by draining the command queue.
There is no queue yet at that point, nor a fence, and
d3d12_queue_drain() called through both: a read of address zero, and
RetroArch went down instead of reporting that the video driver could
not be opened.

It returns when there is no queue or no fence. Nothing was queued, so
there is nothing to wait for.

Found by running the Windows build under Wine, where Direct3D 12 does
not start: it was a page fault in d3d12_queue_drain, and is now
"Cannot open threaded video driver" and a clean exit.
2026-10-03 12:08:08 +02:00
LibretroAdmin 2464feb0d7 input: input_keyboard_event_now() is declared for a build without threads
9b9b659e put keyboard events from another thread on a lane the poll
takes, and input_keyboard_event() - compiled either way - ends by
calling input_keyboard_event_now(), which is defined after it. The
forward declaration sat inside the lane's HAVE_THREADS block, and the
one meant for a build without threads came after the call. Emscripten
is the job that builds this file without threads:

  input/input_driver.c:10153:4: error: call to undeclared function
  'input_keyboard_event_now'
  input/input_driver.c:10157:13: error: static declaration of
  'input_keyboard_event_now' follows non-static declaration

One declaration, ahead of the HAVE_THREADS block, replaces both.

The compile matrix had no lane for input_driver.c without threads, so
only the Emscripten build could see this. It has one now: it fails
with those two errors before this change and passes after.

Also compiled with clang and the Emscripten job's defines, the
Emscripten-only ones aside: two errors before, none after. Not built
with emcc here.
2026-10-03 12:01:55 +02:00
LibretroAdmin 69f3e94497 input: the frontend starts the input driver for X11 and KMS too; SDL on Windows keeps the setting's
Two things, one of them a repair to a8bc29e62.

That commit had the frontend start the input driver for a Windows
window and decided that it was one from the display type. SDL's video
drivers on Windows report a Windows display too, bring no input driver
of their own, and until then got the one the setting names. With that
commit they got raw input or DirectInput whatever the setting said -
input_driver = "sdl2" with video_driver = "sdl2" came up with
DirectInput. Not run into by anyone, found on reading the display
types for the next step.

So the kind of window is not guessed any more. A video driver or
context that used to start the input driver for its window now says
so: input_driver_left_to_frontend(kind, ...), which clears the two
slots and notes the kind. video_driver_init_input() starts the driver
that goes with it; a driver that says nothing gets the one the
setting names, as it always did. SDL says nothing.

With that in place the X11 and KMS contexts go the way the Windows
ones went: x_ctx and x_vk_ctx say an X11 window, drm_ctx and
khr_display_ctx say a display with no window system, and the frontend
calls input_driver_init_x11() or input_driver_init_kms() - udev if
that is the setting and it starts, the X11 driver otherwise; on KMS
udev or linuxraw when the setting is one of the two, and the
setting's own otherwise. No file under gfx/ calls any of the three
now, and CI holds that.

That is the last of Phase 2's items: which input driver a window
gets is decided in input/, and it is the frontend that starts it.

samples/input/x11_smoke, new, run in CI by the job that already
builds retroarch with X11 and OpenGL and runs it under Xvfb: keys are
pressed in the window, with threaded video and without, and the
small core of the Windows run - it now builds for Linux as well -
says what reached it. It must see its button and its key events
before and after a fullscreen toggle restarts the video driver, every
key event on its own thread, and the frontend must not fall back to
picking the input driver by name. Both scenarios pass here.

Also run here: the seven scenarios of retroarch.exe under Wine, the
frontend-overhead harness, and the choice-function test. Not run: the
KMS contexts, which need a display this box does not have, and SDL
video on Windows, which is back on the path it was on.
2026-10-03 11:58:39 +02:00
libretroadmin ca3d919a75 rthreads: on Windows a timed eventcount wait ends on a high resolution timer
A bounded wait's kernel timeout ends on the system timer's tick, 15.6 ms
unless something in the process has lowered it, so a short timed wait on
an eventcount could run a whole tick long: the threaded video
presenter's repeat deadline and the timer pacing's wait for the worker
among them. A bounded wait now also sleeps on a high resolution
waitable timer, one per thread, together with the thread's event, on
every sleep tier; the waker signals that event for a waiter that sleeps
on one. Unbounded waits are unchanged.

Where the high resolution timer cannot be had, before Windows 10 1803,
the kernel timeout bounds the wait as before. RETRO_EVENTCOUNT_HIRES=0
keeps it alone for comparison, and the test's documented Windows build
links again.
2026-10-03 11:54:19 +02:00
LibretroAdmin a8bc29e62b input: the frontend starts the input driver for a Windows window
Every Windows video driver and context started the input driver for
its window itself: nine of them called input_driver_init_windows()
from their own start-up and handed the result back. With threaded
video that start-up runs on the video thread, so the input driver was
made there - by a thread that never polls it.

They hand back none now. Once the video driver is up,
video_driver_init_input() - the frontend, on its own thread - starts
the input driver for the window: the one kept from before the
restart; for a Windows window, the one input_driver_init_windows()
chooses (raw input if that is the setting and it starts, DirectInput
otherwise); failing those, the one the setting names, as before. The
video side says only what kind of window it put up. UWP and Xbox go
the same way, every video driver there being of the Windows family.
The test input driver, when it is the setting and has a file to play,
is still left to the setting.

What moves with it:

- DirectInput's devices are made on the frontend's thread, the one
  that reads them, with the window as the cooperative-level window as
  before.
- Raw input taken by the window's thread (RETROARCH_RAWINPUT_POLL=0)
  has its window on the frontend's thread. Its messages are then
  pumped by the thread that polls, so what the poll reads is no longer
  a video frame old: under Wine the old path's own measurement went
  from 12 ms in the menu to 0.02 ms.
- Read by the poll, raw input makes its window at the first poll and
  is unchanged.

The d3d11 driver started the input driver after its swap chain so
that it was started for the window that stays; that is true of every
driver now.

X11 and KMS contexts still start their own (input_driver_init_x11(),
input_driver_init_kms()); that is the rest of this.

CI: no file under gfx/ may call input_driver_init_windows().

Built for Windows with the d3d9, d3d10, d3d11, d3d12, wgl and gdi
drivers; d3d8 and d3d9cg carry the same two-line change and were not
built. The seven scenarios of the run of retroarch.exe under Wine
pass - raw input, the old raw path, DirectInput, threaded video on
and off - and each now also wants the frontend never to fall back to
picking the driver by name. The frontend-overhead harness passes.
2026-10-03 11:39:53 +02:00
libretroadmin 04388402db video thread: on Windows the display pacing hold sleeps on the high resolution timer
The hold waited for its target with a timed wait on the frame ring,
which on Windows is a kernel timeout ending on the system timer's tick:
15.6 ms unless something in the process has lowered it, as some GPU
drivers do and others do not. On drivers that leave it alone, D3D11 and
D3D12 among them, every hold overshot to that tick, so the loop ran
well under the display's rate, frames were dropped and audio starved.

On desktop Windows the hold sleeps with retro_sleep_us(), on the high
resolution waitable timer. A ring wake only re-read the clock, so
nothing is lost; elsewhere the wait is unchanged.
2026-10-03 11:33:20 +02:00
LibretroAdmin 9b9b659eda input: keyboard events from another thread wait for the poll
input_keyboard_event() acts on a key at once: the menu, the on-screen
keyboard, the frontend's own key handling, and the core's keyboard
callback. All of that belongs to the thread the frontend and the core
run on.

Most drivers report their keys from the poll, on that thread. The
Windows window procedure and X11's event loop report them from where
the window's messages are handled, and with threaded video that is
the video thread. So with threaded video and any Windows input driver
but raw input (which reads at the poll), every key pressed ran the
menu's and the frontend's key handling on the video thread, and
called the core's keyboard callback there, while the core ran its
frame on the other. Run for real - retroarch.exe under Wine,
DirectInput, threaded video, a core that reports the thread its
callback is called on - five key events of five came on a thread that
was not the core's. With threaded video off, or raw input, none.

An event reported on another thread now goes into a queue, and the
next input_driver_poll(), on the frontend's thread, takes the queue
in order and does with each what used to be done at once. Reported on
the frontend's own thread, an event is acted on at once as before, so
every driver that reports from the poll is unchanged. The window's
thread hands its keys over once a frame anyway, so the poll that
takes them is the first that could have seen them.

The poll is the one reader and takes no lock. Writers take turns
through a flag; there is one in practice. The queue holds sixty-four
events; with no poll for that long the newest are dropped, counted,
and a warning is logged once.

This is the first of the plan's event lanes (WP-04).

samples/runloop/frontend_overhead, a new lane: keys reported from a
second thread while frames run reach the core's callback all of them,
in order, on the core's thread; reported on the frontend's thread
they are acted on at once; a hundred with no poll between leaves the
core sixty-four, in order, and thirty-six counted as dropped.

samples/input/windows_wine_smoke: the test core says so in the log
if a key event reaches it on a thread other than the one retro_run()
is on, and every scenario wants none. All seven pass with
retroarch.exe built from this; the DirectInput one did not before.
2026-10-03 11:23:28 +02:00
LibretroAdmin 31f2d6b4e2 docs: say what --appendconfig does to the saved config
--appendconfig loads extra config files over the main one. The help
text and the man page say that and stop. What they leave out is what
surprises people: once loaded, the appended settings are settings
like any other, and when the main config is saved - on exit, with
config_save_on_exit, or from the menu - they are written to it. Start
with --appendconfig once and the next start without it still has the
appended values.

It is long-standing behaviour that scripts and launchers may rely
on, so it stays. Both places now say it,
and say how to use an appended config for one session only: put
config_save_on_exit = "false" in the appended file. The in-tree
test configs in tests-other/ already do exactly that.

Checked with a run of each: with a setting appended, the main config
has it after exit; with config_save_on_exit = "false" appended as
well, the main config is untouched, its own config_save_on_exit
included.

The man page also gave ',' as the separator for several files. The
code splits on '|', as the help text says.
2026-10-03 11:07:37 +02:00
libretroadmin e5c48508a3 d3d11, d3d12: display pacing reads the vblank the last present went out on
The present time the display pacing aims at comes from the swapchain's
frame statistics only while they describe the most recent present. A
present with sync interval 0 and tearing allowed is on no vblank and
leaves the statistics at the last one that was; stepped forward by the
period, that time drifts off the display's grid, and with VSync off
every frame was aimed at it.

Otherwise, and when the statistics cannot be had, the compositor's last
vblank is read instead, as the Vulkan driver does on Windows.
2026-10-03 10:57:36 +02:00
LibretroAdmin c9347587c7 win32: a window taken back is drawn at its real size; d3d11 keeps its window by default
With Direct3D 11 and the window kept across a restart, toggling
fullscreen and back left the picture in the top left of the window,
scaled down, with black beside and below it - what the in-place
toggle did with Vulkan before f2be48bfb.

It is the same thing. Setting a window up leaves the sizes the video
driver is told holding figures worked out on the way - the window's
outer size, then one with the menu bar added - and counts on a
WM_SIZE afterwards to put the client area's size there. A new window
always gets one, when it is first shown. A window that was on screen
all along gets one only if its size changes, and where the size it is
asked to be is the size it has, or Windows holds it to another, none
comes: the driver sizes its buffers for a window that is not the one
on screen. f2be48bfb put the client's size there by hand for the
window the toggle restyles, and said a window made by a restart was
not affected - true then, when a restart made a new window. A restart
now takes the old window back, so win32_set_video_mode() does the
same for a window it took. The two share the code.

Vulkan, kept by default since 184d80a48, did not show it: its
fullscreen toggle is in place and restarts nothing, and its swap
chain takes its size from the surface.

And Direct3D 11 keeps its window without being asked. It was run on
Windows 11 with an NVIDIA card with RETROARCH_WINDOW_KEEP=1: a
content load took the window back and presented on it, and the
scaling above was the one thing wrong. RETROARCH_WINDOW_KEEP=0 turns
keeping off for every driver, as before.

The size fix has not been run on Windows; it is the fix of f2be48bfb,
which was. The seven scenarios of the run of retroarch.exe under Wine
pass with this in.
2026-10-03 10:55:43 +02:00
libretroadmin 671333d079 CI: GL, GL core and Vulkan are compiled as one translation unit
The compile matrix builds the three drivers together the way griffin
does for the MSVC and Xcode builds, so a file-scope name two of them
share is a failure here rather than on those builds.
2026-10-03 10:48:21 +02:00
LibretroAdmin 7de25f6e23 d3d11: the window can be kept across a restart (asked for, not yet the default)
The window is kept across a video driver restart for Vulkan and GDI.
This is the first Direct3D driver to join them, and it only does so
with RETROARCH_WINDOW_KEEP=1 in the environment, because none of it
could be run here: Wine's Direct3D 11 crashes in this driver's swap
chain set-up, with or without this change, so what follows has been
compiled and no more.

DXGI has rules about a window that has had a swap chain. Three things
are done for them.

The old swap chain is destroyed before the window is left. Direct3D
11 puts off destroying what is released until the context is flushed,
and for a hardware-rendered core the device and context are cached
and outlive the driver; a swap chain still alive holds its window,
and no second one can be made on it. d3d11_gfx_free() now clears the
context's state and flushes it after releasing the swap chain.

If a swap chain still cannot be made on a window taken from the last
driver, the window is not kept at any price: win32_window_remake()
destroys it and makes a new one, and the swap chain is tried once
more there. On a taken window a refused flip-model swap chain goes to
that, instead of to the older swap model the driver otherwise falls
back to - which would have been a kept window paid for with a worse
way of presenting. The device made for the first attempt is handed
back to the cache so the second uses it.

The input driver is started after the swap chain, not before, so
that it is started for the window that stays.

A Direct3D driver says its window may be kept by giving it a tag,
win32_window_tag("d3d11"), and only a driver with the same tag takes
it: one Direct3D driver does not get another's window. Vulkan and GDI
have no tag and go on as they were.

The seven scenarios of the run of retroarch.exe under Wine (GDI) pass
with this in.
2026-10-03 10:44:16 +02:00
libretroadmin cf0b0afcc8 gl3, vulkan: the menu effects' SPIR-V carries each driver's prefix
The arrays the menu effects are made from sit at file scope in both
drivers, and the unity build (griffin) compiles both into one
translation unit, where the same names are redefinitions. Each driver's
now carries its own prefix, gl3_ and vk_.
2026-10-03 10:40:06 +02:00
libretroadmin c5aae7f87d d3d9_hlsl: the render chain releases the programs it made
Each pass's vertex and fragment programs and the stock ones are
released with the chain's other resources, as the menu effects' already
are, rather than left holding their references until the device goes.
2026-10-03 10:31:40 +02:00
LibretroAdmin 184d80a48c win32: the window is kept across a video driver restart by default
2cb7874f7 left the window up across a restart only when
RETROARCH_WINDOW_KEEP=1 asked for it, because it had been run under
Wine with GDI and nowhere with Vulkan.

It has now been run on Windows 11 with Vulkan on an NVIDIA card,
threaded video, HDR and a hardware-rendered core: loading content
left the window up, the driver that came next took it, made its
surface and swapchain on it and presented; the swapchain was rebuilt
on it four times after that - fullscreen toggles, by the look of the
log - and the quit went as it does. The input driver, kept across the
same restart, was started once in the session.

So it is the default. RETROARCH_WINDOW_KEEP=0 destroys the window
with the driver as before; =2 still leaves it up without taking it,
for the tests.

It is still the Vulkan and GDI families only. Direct3D and OpenGL
windows are destroyed and made again as before, which is why the
foreground workaround of cd6b6b5ad stays: it is theirs now.

Not run on Windows: closing content with the window kept, Vulkan with
threaded video off, and cards other than NVIDIA's.

samples/input/windows_wine_smoke: every scenario now expects the
window left up and taken back at both restarts; one more turns it off
and expects neither. All seven pass with retroarch.exe built from
this.
2026-10-03 10:27:39 +02:00
libretroadmin dbcc033ca6 d3d12, d3d9_hlsl: menu effects are made when first drawn
D3D12 keeps the pipeline state its menu effects are made from and makes
each effect's pipeline the first time it is drawn, rather than all six
at start and only when the menu driver is XMB; D3D9 HLSL compiles each
effect's program when it is first drawn rather than all six when the
render chain is made. An effect that cannot be made is not drawn and is
not tried again, where one failure used to fail the driver's start on
D3D12 and turn every effect off on D3D9.

The effect programs D3D9 HLSL has compiled are released with the
chain's other programs, where they were never released before.
2026-10-03 10:26:58 +02:00
LibretroAdmin 2cb7874f78 win32: the window can be kept across a video driver restart
A restart of the video driver - content loaded or closed, a setting
changed - destroyed the window and made another. The window vanished
and came back, lost its place in front of other windows (cd6b6b5ad
works around that), and the driver that came back was nearly always
the one that had left, with no need of a new window at all.

With RETROARCH_WINDOW_KEEP=1 in the environment, the context being
freed leaves its window up, win32_window_keep(), and
win32_window_create() takes it instead of making one: the style, place
and size it would have been created with are applied to it, and what
creating a window sets up for the video family - the Vulkan surface -
is done for it. A windowed window stays where it is.

A window belongs to the thread that made it. With threaded video that
is the video thread, which is now held for the next driver (the commit
before this). Where the next driver cannot take the window - it runs
on another thread, it is of another family, none comes - the window is
taken down by its own thread: at once if that is the thread making the
new window, otherwise by win32_window_release_kept(), which the
frontend calls when the next driver is up or has failed to come up,
and which ends a held video thread to do it. Destroying a window that
was left up is not taken for the program being closed, which
WM_DESTROY otherwise means.

Kept by the Vulkan and the GDI families. Not kept when the program is
shutting down, nor from exclusive fullscreen with the display's mode
changed.

It is off unless asked for because it has not been run on Windows. On
Windows the next step, once it has, is to make it the default and take
the foreground workaround out.

win32_window_init() no longer fails when the window class is still
registered, which it is while a window is kept.

samples/input/windows_wine_smoke, three more scenarios, through a
fullscreen toggle and a content close: the window left up and taken
back twice, with threaded video and without; and left up and not taken
(RETROARCH_WINDOW_KEEP=2, which exists for this), where it is
destroyed, a new one made, and RetroArch keeps running. All eight
scenarios pass with retroarch.exe built from this. Under Wine the
window's position and size at each stage are the same kept as new,
windowed to fullscreen and back.

That is GDI under Wine. Vulkan on the kept window - a surface made on
a window that has had one - has not been run anywhere.
2026-10-03 10:13:41 +02:00
LibretroAdmin 7e65caf284 video thread: the thread can be held for the driver that comes next
The threaded video wrapper made a thread for each driver and joined it
when the driver was freed, so a restart of the video driver ended one
video thread and started another. Anything that belongs to its thread
went with it - on Windows, the window, which only the thread that made
it can use or destroy and which is destroyed when that thread ends.

The wrapper's loop now runs on a host thread. Nothing changes by
default: when the loop returns, at CMD_FREE, the thread is ended and
joined as it was. But something running on the thread while the driver
is being freed can ask for it to be held, video_thread_host_hold(),
and then it waits; the next wrapper's loop runs on it. If no threaded
driver comes, video_thread_host_stop() ends it, and the hook given
with the hold runs on the thread first, to take down what was left on
it.

Nothing asks yet; the next commit does, for the Windows window.

samples/gfx/threaded_video (two init-and-free cycles of the wrapper):
37 lanes pass, as on master. Its pacing-rate lane fails here on master
too - 117 fps for 120 on this one-core box - and is not this.
2026-10-03 10:13:37 +02:00
libretroadmin 6699f86df6 vulkan, gl, glcore, d3d10, d3d11: menu effects are made when first drawn
Each menu effect's pipeline, program or shaders are made the first time
the effect is drawn rather than all six when the driver starts, since at
most one is ever on screen: with effects off none is made, and changing
the setting makes the new one then. Vulkan keeps the pipeline state the
effects are made with, for its own render pass and the SDR one, and
makes each from it when asked; GL core and the GLSL backend compile one
program at a time; D3D10 and D3D11 compile one effect's shaders, no
longer only when the menu driver is XMB.

An effect that cannot be made is not drawn and is not tried again until
the driver is restarted, rather than failing the driver's start.
2026-10-03 10:12:42 +02:00
LibretroAdmin 383c57589e gekko: loaders that number the USB drive
WiiFlow starts programs and content from "usb1:"; that is "usb:" here.
2026-10-03 10:03:40 +02:00
LibretroAdmin 957da8c1e8 gekko: netplay starts the core it needs
A netplay session on content for another core runs that core with
netplay's own options (-H or -C, the content), and RetroArch started
with options parses them itself instead of taking them for a loader's
directory and file name.
2026-10-03 10:03:37 +02:00
LibretroAdmin 07eea5d21a gekko: SD cards and USB drives come and go on the Wii
A thread checks once a second: a card or drive taken out is unmounted
and one put in is mounted.  An unmounted volume keeps its C library
device, so files and directories still open on it fail with EIO until
closed instead of reaching freed memory, and the same name mounts
again.  The USB drive search looks at each device once while it stays
plugged in.
2026-10-03 10:03:32 +02:00
LibretroAdmin 440c704e6c gekko: salamander
Makefile.wii.salamander builds on os/gekko with HAVE_LIBOGC=0.  It
runs the core retroarch-salamander.cfg names, or the first one in its
directory, and hands it the content a loader passed.
2026-10-03 10:03:30 +02:00
LibretroAdmin cd6b6b5adb win32: the window a driver restart makes keeps the foreground
Loading or closing content restarts the video driver, which destroys
the window and makes another. Started from a terminal, with the
content chosen on a controller, the new window came up behind the
terminal.

When the window goes, Windows hands the foreground to the next window
down - the terminal's. The new window then asks for the foreground,
as it always has, and is refused: Windows lets only the foreground
process, or the one that got the last keyboard or mouse input, take
it, and the last such input was the Enter typed in the terminal.
(Started from Explorer and driven by keyboard or mouse, the last input
was RetroArch's own and the request was granted, which is why this
did not show there.)

If the window about to be destroyed is the foreground window, that is
noted, and the process gives itself leave to take the foreground,
with AllowSetForegroundWindow(), while it still has it. The window
made next asks as before; if it is still refused - any keyboard or
mouse input in between withdraws the leave - it attaches to the input
queue of the thread that has the foreground for the one call. That
second step only runs for a window replacing one that had the
foreground within the last fifteen seconds: a window that was in the
background stays there.

AllowSetForegroundWindow() is looked up by name, since not every
user32 this file is built for has it.

This does not make the restart reuse the window, which is what it
should do and would make all of this unnecessary; under threaded
video the window belongs to the video thread, which ends with the
driver, and Windows destroys a thread's windows with it. That is the
change this stands in for.

Not run on Windows: Wine has no foreground rule to be refused by. The
five scenarios of the run of retroarch.exe under Wine pass with it.
2026-10-03 09:43:54 +02:00
LibretroAdmin 270b5c5133 win32: turning logging off closes the log console
With Logging Verbosity on and RetroArch started from Explorer, a
console window titled Log Console opens. Turning Logging Verbosity
off was meant to close it - detach_console() calls FreeConsole() -
and it stayed open.

Since Windows 8 a console's handles are real handles to the console
device, and FreeConsole() closes none of them. attach_console() opens
stdout and stderr on CONOUT$, and AllocConsole() gives the process
standard handles on the console; all of those still referred to the
console after FreeConsole(), and a console's window stays for as long
as anything refers to it. The comment there said the streams were not
reconnected to anything because they had not been connected to
anything to begin with, which is so, and is why they kept the window.

detach_console() now lets go of them first: the streams it opened on
the console are pointed at NUL - not closed, since the log writes to
stderr and needs somewhere to write until logging is turned on again
- and the standard handles that are a console's are closed and
cleared. attach_console() notes which streams it opened, so that only
those are touched. Turning logging on again finds nothing connected
and opens a console as before.

The sequence was run twice over in a small program under Wine: the
second console opens and takes output, and stderr stays writable in
between. Wine closes the window either way, so that the window now
closes on Windows is not shown by that; it has not been run on
Windows.
2026-10-03 09:43:51 +02:00
libretroadmin 1bbac30cfc d3d12, metal: menu meshes are drawn natively
D3D12 draws gfx_display meshes through a pipeline of their own on the
existing root signature: vertices and indices in one upload buffer per
mesh, read as stored, and the vertex stage's constants in a ring of
256-byte slots in an upload buffer kept mapped. Each slot is tagged with
the fence value of the frame that uses it; a draw that finds the next
one still in flight, or an HDR render target the pipeline was not built
for, is streamed through draw() as before. The quads' constant buffer,
pipeline, topology and vertex buffer are bound again afterwards.

Metal compiles its mesh shader from source the first time a mesh is
drawn, and draws each mesh from buffers of its own with the constants
passed inline; a device that cannot compile it streams its meshes. The
Metal stubs carry the vertex formats, index type and indexed draw this
uses.
2026-10-03 09:42:28 +02:00
LibretroAdmin f56801ff50 winraw: the old path says how old the state was when the poll read it
Reading raw input by the poll was argued to lower latency and never
measured: taken by the window's thread, a report gets into the state
when that thread pumps its messages - the video thread, once a frame -
and the poll reads the state as of that pump, so whatever a device has
sent since waits a frame more. Read by the poll that wait is not
there. How long it was depends on where in the frame the video
thread's pump falls against the poll, which nobody had looked at.

With RETROARCH_RAWINPUT_POLL=0 - the driver as it was - the thread
that made the window now stamps the clock each time its pump has run
dry, the poll takes the stamp's age, and the driver logs the average
and the worst when it is freed:

  [WinRaw] Taken by the window's thread: at the poll, what that thread
  had taken was 12.06 ms old on average and 14.37 ms at the most, over
  169 polls.

That age is the latency reading by the poll removes: of what arrives
in a frame, the share that lands between the pump and the poll is the
share that was a frame late.

Nothing is added to the default path: the pump's call is in the
branch a thread takes only when its raw input is not read by the
poll, and returns on one compare unless a driver of the old kind is
running on that thread. The clock is QueryPerformanceCounter(), so
the driver gains no dependency.

The figure above is from retroarch.exe under Wine with GDI, in the
menu at 60 Hz; with content running unsynced there it was 0.5 ms.
They show the measurement working, not what Windows does: that takes
a run on Windows with RETROARCH_RAWINPUT_POLL=0.

samples/input/windows_wine_smoke: the old-path scenario wants the
line once for each time the driver was freed and prints it; the
others want none.
2026-10-03 09:19:37 +02:00
LibretroAdmin 34b717d628 gekko: the Wii Remote's pointer
The remote's IR camera is switched on and configured at the system's
sensitivity, the sensor bar powered, and remotes report in mode 0x37:
buttons, accelerometer, the camera's basic dots and the extension's
bytes.  Register writes and reads go out one at a time, each when the
last is answered.  ir.c turns the dots into the pointer: the bar's
midpoint with the roll taken out, offset for a bar above or below the
screen, held across a dot dropping out.

The native joypad driver hands it to gx_input as mouse and lightgun
for remotes and Nunchuks.  gx_input keeps one pointer per port and
reads every port, where it indexed a resized array by port but filled
it by remote count.

wiimote_test checks the pointer turning with WMPADS=wiimote-ir.pipe
(run-dolphin.sh feeds remote 1 too); the host tests check the pointer
from synthetic dots.
2026-10-03 09:15:40 +02:00
libretroadmin a1dfb374d1 gfx_display: effects are drawn from their mesh on every driver
The coordinate array in gfx_display is gone. A mesh made with
GFX_MESH_FLAG_POSITIONS keeps each vertex's x and y as plain pairs
alongside its vertices, the full-screen quad has its own, and while a
driver's pipeline code draws an effect gfx_display_effect_coords()
hands it those of the mesh the effect is drawn over; an effect is drawn
over nothing else. GL, GL core's filter-chain path, D3D8, D3D9 (Cg and
HLSL), GX2 and Metal stream their effects from there, so each effect is
drawn over its own mesh: the ribbon over its grid, the per-pixel
effects over the full-screen quad.

Vulkan, D3D10, D3D11 and D3D12 draw their ribbons only from the ribbon
mesh's buffer, and D3D10-12 no longer keep a ribbon buffer of their
own.
2026-10-03 09:09:02 +02:00
LibretroAdmin 8c9a07555d input: the driver that can outlive the video driver is kept across content loaded or closed
A driver that says it survives the video driver (winraw, read by the
poll) was kept across a reinit - a fullscreen toggle that restarts, a
video setting changed - and nowhere else. Loading content and closing
it free the drivers and start them again by another road,
retroarch_main_init_drivers(), which passed no reset flag, and the
keep was tied to that flag. So on the commonest restart of all the raw
input driver and its joypad driver were still destroyed and started
again: closing content logged "[RawInput Joypad] Destroyed" and the
controller came "back, in slot 0".

That road frees the drivers and starts them a few lines later, with
nothing run in between. It now says so, with
DRIVER_LIFETIME_SESSION_SWITCH, and driver_uninit() keeps the input
driver across it as across a reset. Nothing else reads the new flag:
what a core's unloading ends still ends.

One thing a content load can do that a reset cannot: bring a config
override in, or take one out. The settings that decide which port a
controller gets - whether controllers are configured at all, the
profile directory, the number of ports, the port reservations - are
read when the joypad driver reports a controller, which it does when
it starts. They are noted then, as one number, and if they are not
the same when the driver would be kept, it is not: it restarts and
the controllers are given their ports afresh. The same holds for a
reset after one of them was changed in the menu.

main_exit() drops a driver that was kept for drivers that then never
started.

samples/runloop/frontend_overhead, the kept-input lane: kept across
the session switch with its joypads and its controller's registry
record; restarted when a port reservation has changed since the
joypad driver started, kept again once the new driver has seen it.

samples/input/windows_wine_smoke: every scenario now closes the
content, with a hotkey, before it quits. The video driver starts
three times; the raw input driver once where it is kept and three
times where it is not; Escape still quits from the menu afterwards.
All five pass with retroarch.exe built from this.

Not run on Windows. On Windows with Vulkan the fullscreen toggle is
in place and restarts nothing, so this is the first restart there
that the driver is kept across: RETROARCH_INPUT_KEEP=0 is the way
back if it misbehaves.
2026-10-03 09:08:40 +02:00
LibretroAdmin 94c0211688 menu: Input Information, the controller in each port, under Information
The port rows leave System Information for a submenu of their own.
Every port with a device in it is listed, marked "No Autoconfig
Profile" when none matched it; before, only matched devices showed.
Each row carries its port, so the sublabel reads that port's details
instead of matching the row's text back to one.
2026-10-03 08:56:52 +02:00
libretroadmin ac4250bab9 d3d10, d3d11, d3d12, metal: menu meshes and the effects' shared clock
D3D11 and D3D10 draw gfx_display meshes themselves: each mesh in
immutable buffers of its own, read as stored through an input layout
for the 20-byte vertex, under a shader that takes the quads' MVP and a
tint, blended, with the quads' shader, buffers and topology bound again
afterwards. Their ribbon pipelines draw from the ribbon mesh's buffer.
D3D12's ribbon does too, from an upload buffer per mesh that is retired
behind the frame's fence when it gives way, as the font atlas's are;
plain meshes there are still streamed. D3D10, D3D11, D3D12 and Metal
read the effects' clock from gfx_display, one step ahead as each of
them drew it.
2026-10-03 08:35:00 +02:00
LibretroAdmin 95884e65b9 winraw_joypad: its window's messages are looked for every eighth poll
Read by the poll, the joypad driver's reports come out of the bulk
read. What is left for its own PeekMessage() is arrivals and removals,
and it made that call at every poll: one system call a poll for
something that happens when a controller is plugged in.

It makes it every eighth poll now. A controller plugged in is noticed
at most seven polls later - some 60 ms at 120 polls a
second - and seven polls in eight are a call shorter. The poll after
the window is made looks at once, since the controllers already there
are announced to a new window straight away.

It also answers where the keyboard's and mouse's reports "taken as
messages" came from when video is not threaded, which the run of
retroarch.exe under Wine showed and nothing explained: from this
call. The range it asks for has to have WM_INPUT in it, or the raw
input queue is not looked at and an arrival is never seen; and under
Wine a PeekMessage() for one window, with WM_INPUT in its range, hands
over the raw input of the thread's other windows as well. Found by
marking each pump and logging which one a report came through: every
one came through this. Whether Windows does the same is not known
here - asked for one window, it should not. Either way the window
procedures take what they are handed, and now it is asked an eighth
as often.

samples/input/winraw_joypad_poll: a device change waiting for the
window is taken within eight polls, the poll after a look does not
look again, and the bulk read is still made by every poll. The run of
retroarch.exe under Wine passes its five scenarios with this in.
2026-10-03 08:27:21 +02:00
LibretroAdmin add3080afe gekko: USB HID pads on the Wii
os/gekko's USB layer also drives /dev/usb/hid, where IOS puts HID-class
devices: gk_usb_hid_list lists them, and the existing calls take their
ids, with the interface's description and interrupt transfers in the
hid form.  At exit (and before gk_exec) waiting transfers on open
devices are cancelled and both interfaces shut down and close.

The native Wii build gets the "wiiusb" HID driver back: a worker
follows the device list and gives each pad the joypad connection layer
knows a reader thread on its interrupt IN endpoint; queued control
messages go out from the worker.  It also implements set/get_report,
set_idle and set_protocol, which the DualShock 3 driver needs.

usb_test talks to Dolphin's emulated Skylander portal on the hid
interface (run-dolphin.sh plugs it in) and checks the shutdown.
2026-10-03 08:26:57 +02:00
LibretroAdmin d5e2f7ed73 CI: the Windows run under Wine has a core, and a DirectInput scenario
The run of retroarch.exe under Wine pressed keys in the menu and
checked that the frontend acted on them. It now runs a core,
smoke_core.c, built as a DLL by the script: it needs no content and
writes to the log when port 1's B button changes and every key event
its keyboard callback is given. So what is checked is input as a core
gets it, by both of the ways a core gets a keyboard: through a bind,
and through the callback.

Each scenario presses J (bound to nothing) and Z (the default bind for
B), toggles fullscreen, which restarts the GDI video driver, presses
both again and quits with Escape. In every one the core must have seen
B pressed and released before the restart and after it, and J's
key-down and key-up, in that order, both times.

A fifth scenario runs the DirectInput input and joypad drivers. They
are started by input_driver_init_windows() like the raw ones, since
7c3b82c23, and had not been run since: they start, are restarted with
the video driver, and the core sees the same.

Seen on the way, and the same with the raw driver read by the poll and
as it was before: the core is given the key-up of a key that is bound
to something - Z, F - and never its key-down. The frontend keeps the
key-down of a bound key from the core's keyboard callback and lets the
key-up through. Not changed here; the script's header says why J is
the key it checks.

All five pass on master.
2026-10-03 08:18:25 +02:00
LibretroAdmin 923dbada2f apple: stamp the App Store version on extensions too
The fastlane build lanes pass the build number and marketing version
to xcodebuild as CURRENT_PROJECT_VERSION and MARKETING_VERSION, so the
widget and Top Shelf extensions carry the same CFBundleVersion and
CFBundleShortVersionString as their containing app, as App Store
Connect requires.

PACKAGE_VERSION on iOS and tvOS comes from version.all, as on every
other platform, so the App Store build bump never changes the version
the code reports or the netplay compatibility magic derived from it.
2026-10-03 08:09:21 +02:00
LibretroAdmin ddb547b3f2 CI: the Windows raw input path, run for real under Wine
The winraw tests so far take the drivers apart: each includes one
driver file and stands in for the frontend around it. None of them
runs the frontend. This cross-builds retroarch.exe with mingw-w64 and
runs it under Wine on a virtual display - the GDI video driver, the
raw input driver, the raw input joypad driver - presses keys in its
window and reads its log.

samples/input/windows_wine_smoke/run.sh plays four scenarios, each a
key, a fullscreen toggle (which restarts the GDI video driver), a key,
and Escape to quit:

- the default, read by the poll and kept: the input driver starts
  once and is not restarted with the video driver, the joypad driver
  is destroyed once, at the end, RetroArch quits on the Escape pressed
  after the restart, and no report was taken by the thread's pump as
  a message;
- RETROARCH_INPUT_KEEP=0: the input driver is restarted with the video
  driver, and works after it;
- threaded video off: as the first, with the window, the pump and the
  poll on one thread;
- RETROARCH_RAWINPUT_POLL=0: the driver as it was before any of this,
  restarted with the video driver, still working.

All four pass here on master. The third is the arrangement that had
not been run anywhere.

Keys are held for a few frames. The frontend samples the keyboard's
state once a frame, and a press and release inside one frame - which
is what a key tool sends by default - is a state it never sees; the
first attempts at this looked like the keyboard dying after a restart
and were that.

Wine is not Windows. What this holds is that the frontend, the pump,
the two drivers and the restart of the drivers fit together, on every
commit that touches them; what only Windows answers was tried by hand
when the path was made.

Windows-wine-input.yml builds and runs it: about five minutes of
build on one core here, less with the cache.
2026-10-03 08:08:18 +02:00
LibretroAdmin 90f90dce45 gekko: networking on the Wii
os/gekko provides BSD sockets over IOS's IP stack (/dev/net/ip/top):
socket, bind, listen, accept, connect, send/recv(from/to), set/
getsockopt, poll, select, fcntl for O_NONBLOCK, gethostbyname and the
inet_* conversions, with IOS's error codes mapped onto errno.  Sockets
are C library descriptors, so close, read and write work on them.  The
interface comes up on first use, or through gk_net_init, which waits
for DHCP.

The native Wii build turns on networking and achievements.  With
GEKKO_NATIVE, libretro-common takes its POSIX socket paths (with
HAVE_SOCKET_LEGACY), connecting with a timeout by connecting again as on
the 3DS, and rcheevos uses pthreads.

net_test runs TCP and UDP over loopback in Dolphin, whose IOS uses the
host's sockets.
2026-10-03 07:48:55 +02:00
LibretroAdmin fc2fc6fe5f winraw: the driver stays across a video driver restart
The raw input driver says it survives a video driver restart
(input_driver_t::survives_video), and so is no longer freed and
started again at every content load, restarting fullscreen toggle or
change of video setting; its controllers are not found, named and
configured again each time.

It says so when it is read by the poll and the joypad driver in use is
winraw_joypad read by the poll: the two are kept or restarted
together. Their windows are then the polling thread's, not the video
driver's thread's; the driver looks the main window up afresh at every
poll; and the two things it used to pick up only by being restarted -
a mouse plugged in, the Windows-key setting - it does itself since
478f9a09a. With another joypad driver, or with
RETROARCH_RAWINPUT_POLL=0, it is restarted with the video driver as
it always was. RETROARCH_INPUT_KEEP=0 has every restart restart it,
with reading by the poll left on.

samples/input/winraw_poll, under Wine: read by the poll with a joypad
driver that can stay, the driver says it survives; with one that
cannot, or not read by the poll, it says it does not. What the
frontend does with the answer is in samples/runloop/frontend_overhead.

Not run on Windows: a content load and a restarting fullscreen toggle,
with the keyboard, the mouse and a controller tried after each, are
what this needs.
2026-10-03 07:44:07 +02:00
LibretroAdmin 0b268a22e9 input: a driver that can outlive the video driver is kept across its restart
The input driver has always been freed by the video driver's teardown
and started again by its start-up, joypad drivers with it. A content
load, a fullscreen toggle that restarts, a video setting changed, and
every controller is found, named and configured again and the input
driver's own state is thrown away. It was that way because the video
driver made the input driver, and input drivers held the video
driver's window or lived on its thread.

A driver that holds nothing of the video driver's can now say so, with
a new last member of input_driver_t:

  bool (*survives_video)(void *data);

and is left alone when the video driver is only being restarted.
driver_uninit() asks before the video driver is freed;
input_driver_free_with_video() then frees nothing; the video driver's
init gets the kept driver back wherever it would have started one -
input_driver_init_windows() for the contexts that start one, and
video_driver_init_input() for the drivers that start none; and
drivers_init() frees it after all if nothing took it back.

It is not kept on a shutdown, which is not a restart; when the input
or the joypad driver setting is not the one it was started with, a
restart being how such a change is applied; when its data is the video
driver's own; or with RETROARCH_INPUT_KEEP=0 in the environment.

Nor at the next restart after the menu's "reset configuration to
defaults". That throws away the controllers' configuration, which
they get when the joypad driver reports them - when it starts. It used
to be back at the next restart of the drivers, and so that it still
is, that restart is one of the input driver too. (The other resets of
the configuration while running - an override unloaded, a save -
already put the controllers' configuration back themselves.) The
mouse grab is let go when it is kept, being the old window's, and is
taken again by the video driver's start-up as it always is.

No driver names the member yet. The twenty-seven that do not have a
NULL there, which means no, and are freed and started as before:
nothing changes until one opts in.

samples/runloop/frontend_overhead, a new last lane, with the input
and joypad drivers wrapped so that their starts and frees are counted:
a driver that says it survives is not freed or started by a driver
restart, its joypad driver is not destroyed, and a controller keeps
its registry handle through it; one that says no is freed and started
as always; so is one that survives when the joypad setting, or the
input driver setting, is not the one it started with; and so is one
that survives when the drivers are shut down and brought up rather
than restarted; and one asked to restart with the next restart does,
once, and is kept at the one after. With RETROARCH_INPUT_KEEP=0 the
first of those fails, as it should. samples/input/window_driver, under Wine: with a driver
kept, input_driver_init_windows() hands it back and starts none.

Compiles for Linux, for Windows with mingw and through the Android
unity build.
2026-10-03 07:44:04 +02:00
LibretroAdmin 070da834f1 video_thread: synchronous commands without the wrapper's lock
A poster takes the poster slot with a compare-exchange, writes the
mailbox and publishes the command with a release store; the video
thread answers into the mailbox, takes the command down and publishes
the reply, so a poster that sends its next command on seeing the reply
never has it cleared under it. The poster sleeps on reply_ec for the
reply or a waiter call, the video thread on the same eventcount for
that call's done, and a thread waiting for the slot on user_ec. On
Apple a main-thread wait comes up every millisecond to pump the cocoa
trampoline, and cocoa_main_thread_cond_wait_pump() goes.

The viewport last reported to a caller is three atomic words, and the
upload batch reads the poke and driver data directly, both being the
video thread's. The lock remains only for the asynchronous upload
lists on a backend without pointer atomics.

The threaded video harness posts blocking commands from two threads at
once and checks each reply reaches its own poster and each command
runs once on the video thread. The viewport lane no longer holds a
lock that is gone.
2026-10-03 07:43:22 +02:00
LibretroAdmin 489849a7d9 joypad_connection: a vacant slot is claimed, not just found
On the Wii U the input poll connects Wii remotes and the GamePad while
the HID driver's polling thread connects HID pads, both through
pad_connection_find_vacant_pad() into the same slots. Each slot now
carries a claim: the search takes a free one with a compare-exchange
and reads nothing else, and the slot is given back when its pad goes -
deinit and deregister in joypad_connection, the kpad and wpad drivers'
deregister, and wiiu_hid when a pad's init fails. The end of the list
is a claim state of its own, set by pad_connection_mark_end().

wiiu_joypad reads a slot's input driver once per call instead of
testing it and then reading it again, and looks the driver up by the
joypad index it was queried with. An always-true test of wiiu_hid's
device_name array is dropped.

joypad_slots_test has two threads claim, hold and release slots at
once and checks none is ever held by both.
2026-10-03 07:43:18 +02:00
LibretroAdmin 145609ebef overlay_setter_bounds: read the gekko driver
e62d8258 added gfx/drivers/gekko_gfx.c with an overlay interface, and
the check fails any driver that defines the setters and is not in its
table:

  [FAIL] gekko_gfx.c: defines overlay setters but is not in DRIVERS

The driver is in the table now. Its three setters are bounded: each
takes its image from overlay_at(), which returns NULL unless the page
is loaded and the index is under its count. The check followed only
helpers named *_overlay_sprite / *_overlay_sprite_map, so it follows
overlay_at() as well.

It still has teeth for this driver: with the comparison taken out of
overlay_at(), all three setters fail; with set_alpha writing
overlay[image] directly, that one fails.

No driver source changes.
2026-10-03 07:31:19 +02:00
libretroadmin e3d8786c4a gfx_display: menu drivers draw meshes, and XMB's effects are drawn as meshes
A menu driver can build geometry once - vertices with position, texture
coordinates and colour, triangles or strips, indexed or not - and draw
it every frame through gfx_display with a 4x4 transform, so it may be
3D, and a program: textured, coloured, the stock blend, or one of the
menu effects (ribbon, simple ribbon, snow, simple snow, bokeh,
snowflake). Vulkan, GL and GL core keep each mesh in a static buffer of
their own, read as stored and drawn through a mesh pipeline; every other
driver that takes strips gets the mesh transformed on the CPU and
streamed through draw(). There is no depth buffer: meshes draw in the
order they are asked for. The effects share one clock in gfx_display
in place of the one each driver kept, stepped as before.

XMB's ribbon is such a mesh, built once and freed with the menu, and
its background effects are drawn through it. On Vulkan and GL core the
ribbon pipelines read the ribbon mesh's own buffer; the other drivers
still draw the effects from the coordinate array the ribbon mesh fills
for them. samples/gfx/gfx_display_mesh covers mesh creation, the CPU
path and the effect programs.
2026-10-03 07:28:43 +02:00
LibretroAdmin 4f2636b99f gekko: switching cores runs the other core's program
gk_exec loads a DOL or ELF from memory and starts it with loader
arguments: the image, a small stub and the command line go to the top
of memory, the exit hooks run (FAT volumes synced, audio DMA stopped,
Bluetooth closed), and the stub copies each section into place and
jumps in.  exit() runs the same hooks.  IPC ignores replies to
requests that are not its own.

The native frontend uses it for set_fork: a core switch reads the
core's program before the volumes are unmounted and runs it with the
content as directory and file name, on the Wii and the GameCube.

exec_test runs a copy of itself as an ELF and as a DOL (elf2dol.py),
attached through run-dolphin.sh's ATTACH; the host tests check the
DOL and ELF layouts gk_exec accepts and refuses.
2026-10-03 07:25:37 +02:00
LibretroAdmin 478f9a09ab winraw: a mouse plugged in while running works, and so does the Windows-key setting
Two things the raw input driver picked up only by being started again.
It was started again at every video driver restart - a content load, a
video setting changed - so neither showed for long. Before the driver
can be left running across those restarts, which reading by the poll
has made possible, it has to do them itself.

The list of mice is made when the driver starts. A report from a mouse
that is not on it was dropped: a mouse plugged in while RetroArch ran
did nothing until something restarted the drivers. Such a report now
has the list made again at the end of the poll - the way a restart
makes it, same order, same task to fetch the names - with the mice
that were there keeping their position, buttons and device type. At
most once a second. A handle that is still not a listed mouse after
that is not asked about again, and a report with no handle at all,
which is input something injected, never is.

The "disable Windows keys" setting is part of how the keyboard is
registered, so it took effect at the driver's next start. The poll now
registers the keyboard again when it sees the setting has changed.

Both only when raw input is read by the poll, which is the default;
with RETROARCH_RAWINPUT_POLL=0 the driver is as it was.

samples/input/winraw_mouse_hotplug, the driver under Wine with the
list of raw input devices replaced by one the test controls: a second
mouse's first report puts it in the list by the end of that poll and
its next report moves it, the first mouse keeps its state; an unlisted
handle is asked about once and a report with no handle never; two new
mice in quick succession are one refresh, and the one that had to
wait is picked up a second later; the setting switched on and off is
registered by the next poll. In CI. Not run on Windows.
2026-10-03 07:23:52 +02:00
LibretroAdmin aabe1243e2 win32: the paused loop's wait is not ended by raw input the poll reads
runloop_idle_wait() paces the paused and the unfocused loop: it waits
on the main thread's message queue for up to ten milliseconds, and
returns early if any input is waiting, so that a key is answered at
once.

Since the keyboard, the mouse and the controllers of the raw input
drivers are read by the poll (1df271577, 9d908a485), their reports are
in that thread's queue. A controller that reports a thousand times a
second does so whether or not anything on it moves, and - registered
as it is to be heard in the background - whether or not RetroArch has
the focus. Each report ended the wait. Paused or in the background,
with such a controller plugged in, the loop turned a thousand times a
second where it had turned a hundred: a poll, a bulk read and a
re-present of the frame each time, for nothing. Before the poll read
them those reports were in the video thread's queue and did not wake
the main thread at all.

On a thread whose raw input the poll reads, the wait now leaves raw
input out of what wakes it. The loop still polls on every turn, so a
key or a button is seen within the ten milliseconds, as it was before
any of this; the other kinds of input wake it as they did.

Found by reading, not reported: it costs processor time in the
background and nothing that shows on screen.

samples/input/winraw_poll, under Wine: with a report waiting in the
queue, a wait for any input ends in a millisecond, and a wait that
leaves raw input out lasts its 300. Not run on Windows.
2026-10-03 07:16:45 +02:00
LibretroAdmin 0d4e703ed3 winraw_joypad: two controllers of the same model are two controllers
When a controller arrives, the driver looks for a slot holding the
same device under an old handle. Windows can announce a reconnected
controller's new handle before it announces the old one gone, and the
old slot has to be taken over, or the controller is there twice and
its port is lost.

"The same device" was judged by vendor and product ID. A second
controller of the same model has those too, so it was taken for the
first one reconnecting: the first was evicted and the second given its
slot. Two identical controllers could not be used together - each one
plugged in threw the other out.

A slot with the same IDs is now the same device only if it has the
same device path as the newcomer, or if its handle no longer names a
device: it was unplugged, and this is it coming back, on whichever
port. A slot whose handle Windows still answers for and whose path is
another is another controller, and is left alone. The path is kept
with the pad for that; it was already fetched, for the product
string.

The search no longer stops at the first slot with the same IDs: with
two of a model plugged in, the one reconnecting may be the second.

samples/input/winraw_joypad_same_model, the driver with its HID calls
routed to fake controllers that share one vendor and product ID: three
at once are three slots; one reconnecting on its port, arrival before
removal, gets its own slot back, the others are untouched, no
disconnect is announced, and the old handle's late removal removes
nothing; one reconnecting on another port takes the slot its dead
handle held; a reconnect whose old handle still answers is known by
its path, whatever the case of the letters; with no path, a live
handle is another controller and a dead one is this one back. On the
driver as it was the first check fails: one slot, one connected. In
CI. Not run on Windows, and not with two real controllers.
2026-10-03 07:05:47 +02:00
LibretroAdmin 7a8a79056e winraw_joypad: only the newest report of a bulk read is parsed
On Windows, a DualSense Edge, a minute and a quarter of play: 78777
controller reports in 5575 bulk reads. It reports a thousand times a
second whether or not anything on it moves, so every poll
finds eight to seventeen reports waiting, and each was parsed: a
clear of the button and hat state, a HidP_GetUsages() and a
HidP_GetUsageValue() per axis and hat, to write a state the next
report overwrote before anything read it. Since 9d908a485 that runs on
the main thread, in the poll.

A report carries the controller's whole state. What a poll leaves
behind is the same whether every report waiting is parsed or only the
last one - so only the last one is. winraw_joypad_take_hid() keeps the
reports a bulk read hands it, and winraw_joypad_parse_held() parses
what is kept when the read is done: for that session, one parse a
read where there were fourteen.

"The last one" is per report ID, the report's first byte. A device
may split its state across several IDs, and the parser leaves an axis
alone when the report in hand does not carry it, so a report of
another ID does not make an earlier one redundant. The newest report
of each ID is kept, up to four per controller, and they are parsed in
the order their newest arrived - which leaves buttons, hats and axes
as parsing every report in turn does. A report too big to keep, or a
fifth ID in one burst, is not guessed about: what is held is parsed,
then it.

A record that carries several reports is taken apart into them, each
with its own length. It used to be handed to the parser whole, with
the length of all of them.

A report that arrives as a message is still parsed at once, after
anything held for its controller. With RETROARCH_RAWINPUT_POLL=0
nothing is read in bulk and nothing is held.

When the driver is destroyed it logs, at debug level, how many reports
bulk reads handed it and how many it parsed.

samples/input/winraw_joypad_newest: the driver with its HID calls
routed to a fake controller that splits its state across report IDs,
two of them connected. Twenty thousand random bursts - 469523 reports,
any IDs and values, several to a record, some too big to hold, more
IDs than are held - go to one through the holding path and to the
other a report at a time, and after every burst the two read the same
buttons, axes and hats. Parsing the held reports in the wrong order
fails it by the ninth burst. In CI. Not run on Windows.
2026-10-03 06:57:41 +02:00
LibretroAdmin 9d908a4855 winraw_joypad: controllers are read by the poll, in the same read
The keyboard and mouse are read in bulk by the poll (1df271577). The
controllers of winraw_joypad were not: its window was still made by
init, on the thread that starts the input driver - the video thread,
under threaded video - so its reports were taken by that thread's
pump, once per video frame, a PeekMessage() and a GetRawInputData()
apiece, and not while that thread waited in a present. The poll read
whatever state that had left: a stick or a button up to a frame old.
The driver's own poll, a PeekMessage() for its window made from the
main thread, found nothing there.

Now its window is made by its first poll, on the polling thread, and
its reports are read when the poll asks. The read is the one the
keyboard and mouse driver already makes: a bulk read takes every
report waiting on the thread, and passes a controller's to
winraw_joypad_take_hid(), which has been there since de2cc6ddb for the
case of video not threaded. So the two drivers make one read between
them. winraw_joypad's poll comes first in input_driver_poll() and
makes it, through winraw_queue_read(); winraw_poll() finds it made and
does not read again. With an input driver that is not winraw, that
read is the only one, and takes controllers' reports alone.

The thread's pump leaves raw input in the queue while either driver
has a window on it that is read this way
(winraw_queue_claim_thread()); it was the keyboard and mouse window
alone that said so.

Arrivals and removals stay messages. They come through the raw input
queue, which a range of messages without WM_INPUT in it does not look
at, so the pump does not see them; the driver's poll takes them, with
a PeekMessage() for its window as it has always made.

A controller plugged in restarts the joypad driver from the main
window's thread, which is not the one the window is on. Destroy then
asks the window to close, and it goes when its own thread next pumps;
the restart's first poll makes the new one.

RETROARCH_RAWINPUT_POLL=0 puts both drivers back as they were.

The keyboard and mouse driver's debug line of counts now gives the
controllers' reports among its reads.

Not run on Windows. samples/input/winraw_joypad_poll is the driver
under Wine, started from a second thread: =0 as before; otherwise no
window from init, one from the first poll on the polling thread, that
thread marked, one bulk read a poll; a device change waiting is taken
by the poll; destroy from another thread and from the polling thread;
a restart. samples/input/winraw_poll: a read made for the controller
driver takes the keyboard's reports too, this driver's poll does not
repeat it and delivers what it took. Reports of a controller cannot be
injected under Wine, so their way through the read is by inspection
and by what a pad does on Windows.
2026-10-03 06:47:15 +02:00
LibretroAdmin 6b8225a040 gekko: the port directory is built from a copy of the volume name 2026-10-03 06:45:48 +02:00
LibretroAdmin 1df2715776 winraw: keyboard and mouse are read by the poll by default
Reading the raw input queue in bulk from the poll has been behind
RETROARCH_RAWINPUT_POLL=1. It is the default now, and
RETROARCH_RAWINPUT_POLL=0 turns it off, should something need telling
apart from it.

Tried on Windows with threaded video, Vulkan, a 1000 Hz mouse and a
keyboard, a minute of play: 9714 reports read in 7679 reads - one read
an iteration, 2241 of them with reports in - none taken by the pump as
a message, no key event dropped. The old path's two calls a report
would have been 19428. With nothing happening it is one read a frame
that finds nothing: 604 frames in the menu, 604 reads.

What that buys beside the calls: the state the core reads is what the
devices had sent at the moment of the poll, not what the video
thread's last pump left; key events reach the menu and the core's
keyboard callback from the main thread rather than the video thread;
and the window is the main thread's, not the video driver's.

That run also settles what Wine could not: Windows gives raw input to
a window whose thread does not have the focus. The sink registration
stays as RETROARCH_RAWINPUT_POLL=2 for a system where that turns out
otherwise.

Not tried on Windows: video not threaded, where the window, the pump
and the poll are one thread's, and winraw_joypad's reports can come
out of the same bulk read. samples/input/winraw_poll now runs that
arrangement under Wine - window made by the poll, the pump leaving
the reports, the poll reading them in bulk - and the test's switch-off
case is =0.

The line of counts the driver logs when it is freed is a debug line
now.
2026-10-03 06:41:01 +02:00
LibretroAdmin c1efc6ee18 gekko: loader arguments, posix_memalign, content test
The argument block lives after the '_arg' tag at the entry point, as
the Homebrew Channel and RetroArch's own loader write it; argv is
built from its command line, copied before the heap can reuse it.
newlib leaves posix_memalign to the system; provide it.

run-dolphin.sh passes arguments (ARGS) and drives the GameCube pad
through Dolphin's pipe input (PADS).  content-test.sh starts RetroArch
with a core and a generated NES ROM and checks that it draws and
follows the pad.
2026-10-03 06:40:44 +02:00
LibretroAdmin b2968a37ed winraw: the pump leaves raw input for the poll's bulk read
A session on Windows with RETROARCH_RAWINPUT_POLL=1 and the mouse in
use: 4578 reports, of which 3180 were read in bulk, in 951 reads, and
1398 came as messages; 4805 more reads found nothing.

So the mouse works read this way, and the count of calls is this: the
pump took 1398 reports out as messages, each with a GetRawInputData()
of its own and a bulk read behind it, and the poll made a read every
iteration. That is 1398 messages, 1398 single reads and 5756 bulk
reads - 8552 calls where the old path, two a report, would have made
9156. A few per cent, not the saving a bulk read is for.

The pump got to the reports first because it runs every iteration and
takes whatever is in the queue. On the thread whose raw input the poll
reads, it now asks for the messages below WM_INPUT and the messages
above it and leaves the reports where they are; the two ranges take
turns so that neither waits on the other. This is how Microsoft's own
sample of a buffered read keeps its pump off the reports. The poll's
read is then the only one: for that session, the 4358 the poll made
against 9156.

Every runloop path that pumps also polls - the core's poll, the
menu's, and the explicit ones before the pause, idle and unfocused
waits - so nothing is left in the queue for longer than an iteration,
and the wait on the queue (win32_display_server_idle_wait()) is not
cut short by reports nobody takes.

A pump that is not the frontend's - the loop Windows runs for a menu
or while a window is dragged - still dispatches reports as messages,
and the callback still takes them, one from its message and the rest
in bulk.

Threads whose raw input is not read by the poll pump as before: the
video thread always, and every thread with the switch off.

samples/input/winraw_poll: after a pump with the two ranges, an
ordinary message has been dispatched, six injected reports are still
in the queue with none taken, and the next poll reads the six in one
read.
2026-10-03 06:36:14 +02:00
LibretroAdmin 34d92a3c15 winraw: read by the poll makes one read a frame, not two
Tried on Windows with RETROARCH_RAWINPUT_POLL=1 and threaded video:
20 and then 71 reports, all read in bulk, none as messages. So Windows does
give raw input to a window on a thread that does not have the focus,
which was the thing that could not be known from Wine, and the sink
registration (=2) is not needed there.

It also showed what the first version cost when little is happening.
71 reports in 27 seconds is a keyboard and no mouse; at 120 fps that
is some 3300 frames, and the driver made two reads in each - one
before the thread's pump, one in the poll - of which 71 found
anything. As it was before, those 71 reports cost two calls each and
an idle frame cost nothing.

The read before the pump is gone, and the pump is as it was before
de2cc6ddb. It was there so that the pump would not dispatch reports
one message at a time. Now the pump is left to come across one: the
callback takes the report its message carries, as it always has, and
then reads whatever is waiting behind it in bulk. A burst met by the
pump is one message and one bulk read; a pump that meets nothing
makes no read at all.

That leaves the poll's own read as the only one made unasked: one a
frame, which finds nothing when nothing has been sent. It is what
reading at the moment of the poll costs - the state cannot be known
to be current without asking.

The driver's log line on free now says how many reads found nothing
waiting, so the idle cost is in the log and not only in this message.

d087a820cd's mistake - a bulk read from inside the handler loses the
report that woke it - is not repeated by reading from the callback
here: the message's own report is read first, from the message.

samples/input/winraw_poll: six reports the pump meets first are one
from its message and five in one bulk read, delivered in order by the
poll; with nothing waiting a poll makes one read and a pump none.
2026-10-03 06:27:38 +02:00
LibretroAdmin de2cc6ddb7 winraw: keyboard and mouse read in bulk by the poll, behind a switch
With RETROARCH_RAWINPUT_POLL=1 in the environment the raw input driver
makes its keyboard and mouse window on the thread that polls, and
reads the reports waiting for it in bulk - one GetRawInputBuffer() for
all of them - at the start of every poll and before that thread pumps
its messages. Without the variable the driver behaves as before.

As it is otherwise, the window is made on whichever thread runs the
video driver's init, and each report is a WM_INPUT message taken when
that thread pumps: a PeekMessage() and a GetRawInputData() per report,
once per video frame, and not while that thread waits in a present.

Latency: the poll, on the main thread, reads what the last pump left.
A key or a mouse report that arrived since is in the queue, not in the
state, and the core gets it a frame later. Read by the poll itself,
the state is what the devices had sent at the moment of the poll -
with late polling, the moment the core asks.

Cost: two system calls a report - at 60 fps some 33 a frame for a
1000 Hz mouse and some 270 for an 8000 Hz one. A bulk read is one call
for all of them and there is no message to dispatch. No thread is
added and nothing waits or wakes; the reads are made where the
frontend already is. (Counted from the code, not measured: the driver
logs reports, bulk reads and messages when it is freed.)

And the window is no longer the video driver's thread's, which has to
be true before the input driver can stop being restarted with the
video driver. That is a later step.

What else differs when this is on:

- input_keyboard_event() is called at the end of the poll, on the main
  thread, for the keys of the reports read. Otherwise the callback
  calls it from the window's thread - under threaded video the video
  thread - into the menu's state and the core's keyboard callback.
  The key table is written when the report is read.

- Which modifier keys are held is read from the driver's key table.
  The published value (win32_get_keyboard_mods()) comes from the main
  window's thread and can be a frame behind a report just read; only
  the lock keys' toggles are taken from it.

- A bulk read takes every raw input report waiting on the thread. If
  winraw_joypad's window is on it too (video not threaded) its reports
  come out here and are passed on, through winraw_joypad_take_hid().

The driver's notes record an earlier attempt at bulk reads, d087a820cd,
reverted because keys and mouse buttons stopped working while mouse
movement did not, for a reason not found. It read from inside the
WM_INPUT handler. The report a WM_INPUT message carries has already
left the queue when its handler runs, so a bulk read there never sees
it - and a key press is usually the only report waiting, while a
moving mouse always has more behind it. Nothing here reads from inside
a handler. A report that does arrive as a message, because the pump
got to it first, is taken by the callback as before.

The callback's handling of a report moved into a function the bulk
read shares; with the switch off it does what it did.

RETROARCH_RAWINPUT_POLL=2 also registers as a sink, for the case that
=1 gets no input under threaded video. Raw input goes to a window only
while its application is in the foreground; if Windows judges that by
process, a window on a thread that does not have the focus gets it. If
it judges by thread, nothing arrives; as a sink everything does and
the main window's focus decides what is taken. Wine delivers with =1.

A 32-bit build on 64-bit Windows gets its bulk records laid out for 64
bits; that is allowed for. Compiled with the 32-bit compiler, not run.

samples/input/winraw_poll is the real driver under Wine, with the
focused window made and pumped by a second thread and the driver
started from it, as under threaded video: the switch off leaves the
driver as it was; on, the first poll makes the window on the polling
thread; six keys injected while nothing pumps are read by one bulk
read, none as a message, none left in the queue, and come out of the
poll as six events in order with shift on the right ones; a report
the pump dispatches first is still taken; the pump's hook reads on
the window's thread and not on another; free and twenty restarts. The
two existing winraw tests pass. In CI.
2026-10-03 06:21:19 +02:00
LibretroAdmin e62d8258cb gekko: RetroArch builds and runs on os/gekko
Makefile.wii and Makefile.ngc take HAVE_LIBOGC: 1, the default for
now, builds on libogc as before (EXTERNAL_LIBOGC choosing devkitPro's
or the in-tree copy) and defines HAVE_LIBOGC; 0 builds on os/gekko,
with no libogc at all, defining GEKKO_NATIVE.  RetroArch's own files
choose by HAVE_LIBOGC; libretro-common, which cores compile too, by
GEKKO_NATIVE, so libogc core builds keep its libogc branches.

The native drivers keep the libogc drivers' idents, button numbers
and mode list, so configurations, autoconfig profiles and binds carry
over:
- platform_gekko: SD and USB (Wii) or the SD adapters (GameCube)
  mounted as sd:, usb:, carda:, cardb:; the directory layout under the
  program's own; exit back to the loader.
- gekko_gfx: the frame as a textured quad, RGUI's surface and overlays
  over it, text poked into the EFB, two XFBs swapped on the retrace.
- gekko_audio: the libogc driver's chunk ring on the audio DMA.
- gekko_joypad: GameCube controllers, and remotes with Nunchuk or
  Classic Controller; rumble; reset opens the menu, power quits.
- dispserv_gekko: the console's standard from the VI and SYSCONF, with
  the shared mode rules.
Threads are libsysbase's pthreads on the kernel's, the clock and
memory figures come from os/gekko, and stdout and stderr go to the
debug channel.  Not there yet: networking, USB HID pads, starting
another program (core switching), the IR pointer.

os/gekko/gekko.mk lists the system layer's sources for these builds
and its own tests.
2026-10-03 06:18:56 +02:00
LibretroAdmin a62e5f4895 statistics: how old the input is when its frame reaches the screen
The LATENCY block of the on-screen statistics has had, with threaded
video, the time from the core's handover of a frame to that frame's
present. It gets a second line:

  Input age:  6.10 ms poll to vblank (worst 9.80 ms, last 2 s)

measured from the poll that read the devices for the frame instead of
from the handover. The difference between the two lines is how long
the core ran after it polled, which is the part the poll mode changes:
with late polling the poll happens at the core's first input_state
call, with early polling before retro_run, and this line is where
that shows.

input_driver_poll() stamps the moment the devices were read. The frame
carries the stamp to the video thread in its video_frame_info_t, the
video thread works out the age at the present it already times the
latency at, and the figure is published with the other statistics.

The stamp is a clock read, so it is only taken while the statistics
are shown; otherwise the poll stores a zero and the line is absent. A
stamp from more than a second before the handover is left out: the
core did not poll for that frame (a menu frame, a pause).

Tests:

- samples/runloop/frontend_overhead, the poll-site lane: the stamp
  moves at the site each poll mode polls at, once a frame, and at no
  other - for early, normal and late polling, switched while running,
  which is the case the input plan asks this to cover - every new
  stamp lies inside the stretch of time it was taken in, and with the
  statistics off there is none. The lane's input costs are as they
  were: the poll is about 0.9 us with the statistics off.

- samples/gfx/threaded_video, after the pacing-drain lane: no figure
  with the statistics off, one while they are on that is not less
  than the latency, none again after. (That test's pacing-rate lane
  fails on the box this was written on, with and without this change:
  it cannot hold 120 fps there.)

Compiles for Windows with mingw and through the Android unity build.
Threaded video only, like the latency line beside it.
2026-10-03 06:01:54 +02:00
LibretroAdmin 86f273d1bd input: the input code stops reaching into the video driver's state
The other direction of 5fd9105fb. input/ took video_state_get_ptr()
and worked on the video driver's fields: the overlay code read the
core's geometry and the output's size, fetched the overlay interface
and the driver's data, and showed and hid the mouse cursor through the
poke interface; the two test drivers read the frame count.

Those are calls into gfx/ now:

  video_driver_get_frame_count()
  video_driver_get_core_geometry()
  video_driver_show_mouse()
  video_driver_get_overlay_interface()   the interface and its data

and video_driver_get_output_dims(), which was there already.
video_driver_overlay_interface(), a static in input_driver.c that
reached into the video state from the input side, is the fourth of
these, moved to where the state is.

One file is left: the Android input driver, which creates and destroys
the window surface on the app's lifecycle events and names the video
state seven times doing it. That is lifecycle code that wants moving
as a piece, and running on a device, so it is not in this. A step in
the input samples workflow fails if any other file under input/ names
video_state_get_ptr.

Nothing is meant to behave differently. The three on-screen overlay
tests (overlay shown, overlay with the menu and mouse, LED packs;
threaded video off and on) pass on an X11 and GL build of this; the
overlay texture test and the frontend-overhead harness pass;
video_driver.c and input_driver.c compile for Windows and through the
Android unity build.
2026-10-03 05:44:27 +02:00
LibretroAdmin c404e92f8f librsound: the frontend alone joins the playback thread and resets
A playback thread that hits an error clears thread_active, an atomic,
reports the error and ends; it neither resets the connection nor
detaches itself. rsd_stop() joins it and resets, so a stop that lands
during the thread's own exit joins once and closes the sockets once,
and a later start joins a thread that ended on its own first.

The mutex is gone: the delay counters it covered belong to the
playback thread alone. The control socket is closed only when open,
and the unused rsnd_close_ctl() is removed.

samples/audio/rsound_thread starts and ends a stream fifteen hundred
times against a socketpair - the callback failing, the server hanging
up, the frontend stopping it, and stops racing the thread's own end.
It runs in the TSan lane.
2026-10-03 05:44:05 +02:00
LibretroAdmin 0e3299d01a CI: the GEKKO lanes compile against devkitPro's libogc
compile-matrix.sh's GameCube and Wii driver lanes take libogc's headers
from devkitPro's libogc at v3.1.0, fetched on first use (LIBOGC_DIR
names a checkout to use instead), rather than from the in-tree copy,
which is going away.  They build as the external-libogc builds do,
with EXTERNAL_LIBOGC, and the GameCube lane names its platform, which
libogc's headers require.
2026-10-03 05:42:35 +02:00
LibretroAdmin 7aaf2daf10 autosave: worker sleeps on an eventcount instead of a lock and condvar
The worker asks for a copy by setting an atomic and sleeps on the
autosave's eventcount until the main thread clears it; the main
thread's check is one acquire load per autosave when no copy is asked
for, and a copy, a release store and a notify when one is. Quit is an
atomic too, and the interval is a timed wait on the same eventcount.

autosave_snapshot_test reads the request flag without the lock and
links the eventcount.
2026-10-03 05:38:46 +02:00
LibretroAdmin c2ce1e65d8 gekko: the libogc drivers are named for it
The GameCube and Wii units built on libogc take a _libogc suffix:
platform_gx, platform_wii, gx_gfx, gx_gfx_vi_encoder, dispserv_gx,
gx_audio, gx_joypad, wiiusb_hid and mem2_manager.  Their drivers keep
their idents, so configurations carry over.  Native drivers on os/gekko
come in beside them under their own names.
2026-10-03 05:36:50 +02:00
LibretroAdmin 96955c09e8 wiiu_hid: hold the pad slot across calls into the pad
The Wii U HID driver calls the pad's interface itself, on the
frontend's thread, while its polling thread deregisters pads and
frees their data. pad_connection_acquire() and _release() expose the
slot guard to such a driver, and every wiiu_hid call into a pad now
holds the slot for its duration.

joypad_slots_test covers the register/deregister pair, with the pad
freed by its driver after deregistering, and a reader that calls the
pad's interface under pad_connection_acquire().
2026-10-03 05:34:38 +02:00
LibretroAdmin b9ad335c28 input: which input driver a window gets, in one file with a test
A video context's init still starts the input driver, but four more of
them no longer know which one. The choice was written out in each -
with the input drivers' names in it, and in copies that had drifted
apart - and is a call into input/ now:

  input_driver_init_x11()   the X11 GL and Vulkan contexts
  input_driver_init_kms()   the KMS/DRM and Vulkan display contexts

input_driver_init_windows(), which 7c3b82c23 added for the Windows
ones, moves in beside them: the three are input/input_driver_choice.c,
a file that takes nothing but the settings and
input_driver_init_wrap(), so that it can be run on its own.

samples/input/window_driver does that, with stand-in drivers that
start or fail to according to a mask: for every branch, which driver
comes out, which were tried and in what order, that the joypad
driver's name is passed through untouched, and that the two results
are both set or both NULL. Sixteen cases; eleven run on the host, five
- the Windows choice - built with mingw and run under Wine. Both are
in CI.

Behaviour is as it was, with one exception. The Vulkan display
context only moved off the X11 driver, and only in builds with X11;
it now follows the KMS/DRM context's rule, which also takes over when
the setting is udev and falls back to linuxraw if udev does not start.
With the setting at udev and udev unable to start, that is a keyboard
through linuxraw where there was a failure to start.

The EGL-on-X11 context is left alone: it always took the X11 driver
and never looked at the setting, which may be an omission or may not.

Still where it was: the call is made from the video driver's init, on
that init's thread.

The four contexts compile with the X11, GL, Vulkan and DRM headers;
input_driver_choice.c compiles for Linux, for Windows with mingw and
in the Android unity build; the frontend-overhead harness passes.
2026-10-03 05:29:49 +02:00
LibretroAdmin afb4e85e2c rchd: hold the A/V Huffman decoders on the heap
rchd_decode_avhuff keeps its three plane decoders and the two audio
delta trees in a scratch struct allocated on the first A/V hunk and
freed with the handle, alongside the lookup tables and sample buffer
that already live there. Its frame is 336 bytes, so A/V hunks decode
on an 8 KiB thread stack.
2026-10-03 05:27:20 +02:00
LibretroAdmin 52869518a3 os/gekko: Bluetooth and Wii Remotes
rvl/bt.c is a Bluetooth host for HID devices after the Core
Specification's HCI and L2CAP, over IOS's USB interface to the
controller (/dev/usb/oh1/57e/305): commands as control transfers,
events and ACL data from reads kept queued with IOS, ACL out within
the controller's buffer credits.  It reads the link keys the
controller keeps and leaves it page-scanning, so remotes synced to the
console connect when a button is pressed: the connection is accepted,
the link key request answered, and the remote's HID control and
interrupt channels accepted and configured.  One thread owns all of
it.

rvl/wiimote.c drives the remotes on top: the player LED, a status
request, extension set-up (unencrypted, then the identifier) for the
Nunchuk and Classic Controller, continuous reports with the
accelerometer and the extension's bytes, rumble, and starting over
when an extension is plugged in or pulled out.  gk_wiimote_read()
gives a slot's buttons, accelerometer, sticks and triggers.

The MEM2 buffer pool moves out of usb.c into rvl/iobuf.c for both.

wiimote_test checks in Dolphin that its emulated remote connects to
slot 0, reports about 190 times a second with the accelerometer at
rest, has its extension found with the sticks and triggers at rest
(Nunchuk, or Classic Controller with WIIEXT=Classic), keeps reporting
through rumble, and goes when dropped.
2026-10-03 05:24:31 +02:00
LibretroAdmin 4f955e9e7e joypad_connection: no call into a pad while it is torn down
A HID driver connects and disconnects pads on its own thread while the
frontend reads them every frame, and a pad's deinit frees the data the
readers call into. Each slot now has a live flag and a reader count:
get_buttons, get_axis, rumble, the name, has_interface and packets go
into the pad only while it is live and counted, and deinit, deregister
and refresh take the flag away and wait for the count to drain before
tearing the pad down. No lock is taken.

samples/input/joypad_slots reads a slot from two threads while a pad
is plugged and unplugged three thousand times, with a fake pad that
poisons its data on deinit. It runs in the TSan lane.
2026-10-03 05:24:05 +02:00
LibretroAdmin 7c3b82c232 input: one function starts the input driver for a Windows window
Which input driver a Windows window gets - raw input if that is the
setting and it starts, DirectInput otherwise; XInput or the uwp driver
on UWP; XInput on Xbox - was written out four times in the video code,
drivers' names and all: in the Vulkan context, in the WGL context, in
d3d_input_driver() for the Direct3D drivers, and in the GDI driver. It
is input_driver_init_windows() in input/ now and the four call it.

It is called from where they were, inside a video driver's init, so on
the same thread as before: with threaded video that is the video
thread, where a raw input window has to be made for its messages to
arrive. This does not change which thread starts the input driver.

Two of the copies had a wrong argument, and those two behave
differently now:

- Direct3D 9 (both drivers) passed the joypad driver's name where the
  input driver's name was wanted, so the comparison with "raw" never
  matched and D3D9 always got DirectInput whatever the input driver
  setting said. The window procedure, which goes by the setting, was
  meanwhile routing messages for raw input. D3D9 now gets the driver
  the setting names, like every other video driver.

- GDI passed the input driver's name where the joypad driver's name
  was wanted, so the joypad driver was looked up as "raw" or
  "dinput". It is started by its own setting now.

The three file-level variables that kept the DirectInput handle
(dinput_vk, dinput_wgl, dinput_gdi) were written and never read; they
go.

Compiled for Windows with mingw: the Vulkan and WGL contexts, GDI,
d3d_common.c, d3d10.c and input_driver.c. d3d11.c and d3d12.c do not
compile with the headers here before or after this, and d3d8.c and the
two d3d9 files were not tried; in all five the change is the same one
line as in d3d10.c. Not run on Windows.
2026-10-03 05:08:51 +02:00
LibretroAdmin 80875ecefb fmsynth: MIDI events reach the audio thread without a lock
The event queue is a ring of slots, each under its own sequence word.
A writer claims the next number with an atomic add and fills its slot;
the renderer takes slots in order, stops at one still being written,
and skips one the ring has gone past, so a full ring still loses its
oldest messages. Neither side waits.

Dropped messages are counted, and reported once when the synth is
freed rather than from the audio thread.

samples/midi/fmsynth_queue renders while one and two writers send, and
checks order, overflow, a slot claimed and not yet written, and that
every event under unthrottled writers is applied or counted dropped.
It runs in the TSan lane.
2026-10-03 05:06:34 +02:00
LibretroAdmin c41c17d25d os/gekko: Wii USB and USB drives
gk_usb_* talks to IOS's /dev/usb/ven (USBv5, IOS 57 on): the device
list, kept current by a change request that stays queued with IOS and
is acknowledged before the next; resuming a device and reading its
interface and endpoints, alternate settings; and control, bulk and
interrupt transfers, whose data goes through a pool in MEM2, where IOS
wants it.

disk/usbmsc.c is USB mass storage after the Bulk-Only Transport
specification: command and status wrappers, stalls cleared on the
endpoint that stalled, reset recovery when the exchange breaks, and
SCSI block commands (INQUIRY, TEST UNIT READY with REQUEST SENSE until
the unit spins up, READ CAPACITY, READ and WRITE), switching to the
16-byte commands past 2^32 blocks.  It takes the first LUN with a
medium, at 512 to 4096 bytes a block.  gk_usbstorage_open() finds the
first bulk-only SCSI interface among the USB devices and brings it up.

os/gekko/tests/host runs usbmsc.c against a model drive that holds the
host to the transport's rules and throws stalls, unit attentions and
phase errors at it, for a single-LUN drive that stalls GET MAX LUN, a
4 KiB-block drive whose first LUN has no medium, and a 3 TiB one, then
mounts FAT over it.  usb_test checks in Dolphin, against its emulated
Wii Speak, the device list, both alternate settings' descriptions,
control transfers both ways and a bulk transfer.
2026-10-03 05:06:05 +02:00
LibretroAdmin 5fd9105fb5 input: the video code stops reaching into the input driver's state
gfx/ took input_state_get_ptr() and worked on the input driver's
fields itself. video_driver_free_internal() freed the input driver and
destroyed the joypad drivers, cleared a flag and a pointer;
video_driver_init_internal() handed the addresses of current_driver
and current_data to the video driver's init, and read and set the
mouse-grab flag; video_driver_build_info() read two flags out of the
struct every frame; the Win32 window procedure read one, and the SDL3
common code compared the current driver.

Those are five calls into input/ now, with the statements they
replace moved as they stood:

  input_driver_free_with_video()       the teardown
  input_driver_video_slots()           the two slots an init may fill
  input_driver_get_current()           the driver in use
  input_driver_get_flags()             the flags, one read
  input_driver_grab_mouse_for_video()  grab, and note that it is

No file under gfx/ names input_state_get_ptr any more - it was
fifteen places when the input plan counted them - and a step in the
input samples workflow fails if one comes back. The video driver's
init still starts the input driver; that it does so through its own
init's parameters is the next part of this, not this one.

video_driver_build_info(), which runs every frame, makes one call for
the flags word where it made one for the struct pointer.

Nothing is meant to behave differently. The frontend-overhead harness
passes in its three poll-mode runs, a menu harness and the window
procedure's Wine test pass, and video_driver.c and input_driver.c
compile for Windows and through the Android unity build. The SDL3
line was not compiled here.
2026-10-03 05:01:26 +02:00
LibretroAdmin 8674c1f40b libusb_hid: adapters without a lock
Reads and removal belong to the event-handling thread, so the read
path keeps its in-flight flag plainly and takes nothing per report.
Commands from any thread are pushed on a lock-free stack; whichever
sender claims the OUT transfer with an atomic exchange sends them in
order, one at a time, and looks again after letting go for one pushed
meanwhile. The queue keeps its 4 KiB bound.

An adapter counts the send_control() calls inside it and is not freed
under one; the last to leave a removed adapter wakes the poll thread
to free it.

libusb_hid_test sends from the frontend and from the pad handler at
once and checks every command goes out in each sender's order, races
thousands of hand-offs, and unplugs with a sender held inside, with
and without a send in flight.
2026-10-03 04:57:12 +02:00
LibretroAdmin 51287c2d5a vulkan: the HDR off-screen buffers are rebuilt once for a resize, not twice
daf76c56f made vulkan_hdr_buffers_init() the one place the HDR
off-screen buffer and the readback image are made, and called it from
vulkan_check_swapchain() so that they follow the swapchain when it is
rebuilt. The end-of-frame resize kept its own handling as well: throw
both away, resize, make both again. For one change of size both run -
the resize rebuilds the swapchain, which sends the next frame through
vulkan_check_swapchain() - so two render targets the size of the
screen were made, destroyed and made again, with a wait for the
driver's own submissions each time.

vulkan_hdr_buffers_init() now does nothing when the buffers are there
at the swapchain's size, and the end-of-frame resize no longer throws
them away first. Whichever place gets to a new size first rebuilds
them; the other finds them right. A swapchain rebuilt at the same size
(a swap interval change) leaves them alone, as it did before
daf76c56f.

Compiled for Windows with mingw with the HDR code in. Not run: there
is no HDR swapchain on the software driver here.
2026-10-03 04:52:47 +02:00
LibretroAdmin f2be48bfb8 win32: an in-place fullscreen toggle tells the driver the window's real size
Going from fullscreen to windowed with a hardware-rendered core
running (LRPS2 at 2560x1792), the picture sat in the top two thirds of
the window with black below it. The statistics said the frontend was
drawing for 3186x1240; the window's client area was 3186x1792.

win32_window_set_fullscreen() restyles the window through the steps a
new window goes through, and those leave g_win32_resize_width/height
holding sizes worked out on the way - the window's outer size, then,
with the menu bar on, an outer height with the menu added - counting
on the WM_SIZE of the SetWindowPos that follows to put the client's
size there. That message only comes when the size changes. A window is
not allowed to be smaller than the core's picture (WM_GETMINMAXINFO),
so with a core bigger than the window asked for - 1920x1080 asked,
3186x1792 the least allowed - the window is already at its minimum
when the menu bar step resizes it, nothing changes, no message comes,
and the worked-out height is what the video driver is told.

The toggle now ends by reading the client area and reporting that. A
window made by a driver restart goes through the same steps but is not
affected, since the driver then takes its size from the swapchain it
has just made; that path is left as it is.

One GetClientRect per toggle; nothing on the frame path.

samples/gfx/win32_window_proc has the case: a core bigger than the
window asked for, menu bar on, toggled to fullscreen and back. Without
the change the window grows as it should and the size reported is not
the window's; with it, it is. The test's stand-in menu gets an item, so
that it is a menu bar with a height.
2026-10-03 04:52:43 +02:00
LibretroAdmin f7302df6cb os/gekko: external interface, the clock, and SD cards over SPI
gk_exi_* drives the three EXI channels: device select at a clock rate,
immediate transfers of up to four bytes, and DMA that sleeps on the
transfer-complete interrupt.  The C library's wall clock now starts
from the clock chip's counter on the first read, plus the console's
offset (SYSCONF's IPL.CB on the Wii, SRAM on the GameCube), so time()
and FAT timestamps are real.

disk/sdspi.c brings up SD cards in SPI mode after the Physical Layer
Simplified Specification (CMD0, CMD8, ACMD41, CMD58, the CSD for the
size) and moves blocks with single and multi-block reads and writes,
by block number on SDHC cards and byte address on standard ones.
disk/sdgecko.c is its bus on the EXI: memory card slots A and B and
serial port 2, at 16 MHz once up, blocks by DMA through a MEM1 buffer
when the caller's is not aligned or not in MEM1.

os/gekko/tests/host runs sdspi.c against a model card that checks the
protocol (CRCs where they are required, ACMD41 only after CMD55,
response and access delays, busy after writes, the CMD12 stuff byte,
the write tokens) for standard, version 1 and SDHC cards, then mounts
FAT over it.  exi_test checks in Dolphin that time() starts from the
clock chip, that DMA and immediate transfers agree on SRAM in both
directions, and that a probe of an empty port gives up quickly;
run-dolphin.sh sets Dolphin's clock so the time is known.
2026-10-03 04:52:26 +02:00
LibretroAdmin a16d561975 ffmpeg core: standalone makefile builds again
The unix GL source list is one assignment, without the stray second
SOURCES_C that make took for a file to build, and macOS links
libavutil.

The cores sample lane links the standalone core without GL and dry-runs
the GL build, so a source list that does not resolve fails there.
2026-10-03 04:46:33 +02:00
LibretroAdmin 1834cebbef ffmpeg core: decode and main threads hand over without locks
Video slots carry atomic states on a ring the decode thread fills at
the head and the main thread drains at the tail, and both sleep on the
buffer's eventcount. The audio handshake, seeks and the end of the
decode thread go through atomics and two eventcounts; the audio time
travels under a sequence count, and the track and colour-space choices
are atomics. Only libass keeps its lock.

A decode thread that has no slot or no room while the main thread
waits for audio raises a flag, and the main thread drops the finished
frames or the buffered audio from its own side. A seek ends the decode
thread's waits instead.

ffmpeg_finished_wait_test drives each wait directly: starvation both
ways, a seek over either wait, unload over either wait, and torn reads
of the audio time.
2026-10-03 04:43:08 +02:00
LibretroAdmin c9a750cdac os/gekko: FAT filesystem and the Wii SD slot
fs/fat.c is FAT12, FAT16 and FAT32 with long names, written from
Microsoft's specification, over any gk_blockdev_t.  It finds the
volume on a bare device or in the first FAT partition of an MBR,
keeps a 64-sector write-back cache (FAT sectors go to every copy as
they leave it), allocates file clusters ahead of a write so they come
out contiguous, and moves whole sectors of file data straight between
the device and the caller in runs as long as the chain is contiguous.
Short aliases follow the ~N scheme, all-lower-case 8.3 names use the
case bits instead of a long name, and the FSInfo free count is kept.
Open files follow a rename of their entry; unlinking one is refused.

fs/fat_devoptab.c mounts a volume as "name:" for the C library's file
calls: stdio, stat, directories, rename, mkdir and rmdir, statvfs,
ftruncate, fsync, and relative paths after chdir.  The first volume
mounted is the default device.

rvl/sdio.c drives the front SD slot through IOS's /dev/sdio/slot0:
the size from the CSD, then select, 512-byte blocks, the 4-bit bus,
and multi-block DMA reads and writes, by block number on SDHC cards
and byte address on standard ones; unaligned buffers go through a
bounce buffer.

os/gekko/tests/host runs fat.c over images made by mkfs.fat: files
mtools wrote read back, a tree written here passes fsck.fat and reads
back in mtools, random file operations match a model through a
remount, and the volume fills and empties again; on FAT12, FAT16,
FAT32, partitioned images and 64 KiB clusters.  sd_test does the same
through the C library on a Wii in Dolphin, with standard and SDHC
images from sd-image.sh.
2026-10-03 04:40:41 +02:00
LibretroAdmin 64846baa73 win32/vulkan: the in-place fullscreen toggle is on
With Windowed Fullscreen on, F restyles the window the Vulkan driver
already has - no driver restart, no new device, no audio or input
drivers stopped and started (35f2d183d). It was switched off again
(8083cb12a) when the swapchain rebuild it ends in was followed by a
lost device on an NVIDIA setup with HDR on. That was the HDR
off-screen buffers keeping the old window's size, fixed in the commit
before the switch that let it be tried, and with the fix the toggle
has held on that setup.

RETROARCH_FULLSCREEN_IN_PLACE=0 in the environment turns it off, so a
toggle restarts the drivers as it used to.
2026-10-03 04:28:02 +02:00
LibretroAdmin b738e5d5ad CI: the network clients' Wine step keeps one wineserver
The Wine step failed once in its last 21 runs on master with

  FAIL: NFSv3 across a server restart (w first): wine client error:0:
  recvmsg: Connection reset by peer

That line is Wine's own, from a process that lost its wineserver
before it had a thread id - before main(). The test printed nothing
because it never ran. The same test passes six times out of six here
under Wine on master.

Ubuntu's wineserver wrapper passes -p0, so the server exits with each
tool and the next tool starts another. The step runs its tools back to
back, dozens of them, and the one that failed started
milliseconds after the one before it exited. A tool that connects to a
server on its way out being cut off is the reading that fits; it could
not be made to happen here (60 back-to-back runs, none failed).

The step now starts one persistent wineserver for the runner's prefix
and one for root's, and stops both when it exits. No server goes away
between tools, so there is none to be cut off by. The first process on
each brings up Wine's services, which keep its stdio for as long as
they live, so it is given /dev/null: a later tool whose output is
captured through a pipe would otherwise wait on them.

Run here: the step without its sudo and Samba parts passes and leaves
no Wine process behind; the root prefix's lines on a fresh prefix, as
root; 60 runs of an empty program take 1 second against 154 with a
server per run. Not run on GitHub's runners.
2026-10-03 04:16:43 +02:00
LibretroAdmin b235519f63 cocoa: main-thread trampoline waits on the job's eventcount
The job carries a done flag and an eventcount: -run stores the flag
and notifies, and the caller waits on it in bounded waits that keep
the stall diagnostic. Nothing is allocated per call beyond the job,
and Foundation holding the job until -run returns keeps the
eventcount alive across the notify.
2026-10-03 04:15:52 +02:00
LibretroAdmin 3712ab7be7 win32/vulkan: RETROARCH_FULLSCREEN_IN_PLACE=1 turns the in-place toggle on
The in-place fullscreen toggle stays off by default (8083cb12a). With
RETROARCH_FULLSCREEN_IN_PLACE=1 in the environment the Windows Vulkan
context offers it again, so that the fix for the HDR off-screen
buffers can be tried against the toggle that showed the fault, without
changing what F does for anyone else. To go once the toggle is either
on for good or gone.
2026-10-03 04:13:56 +02:00
LibretroAdmin daf76c56ff vulkan: the HDR off-screen buffers are the swapchain's size
With HDR on, a frame is drawn into an off-screen buffer and composited
into the swapchain image. Its render pass begins on the off-screen
buffer's framebuffer, with the swapchain's extent as the render area:

   rp_info.framebuffer              = backbuffer->framebuffer;
   rp_info.renderArea.extent.width  = VIDEO_SCALE_W(vk->context->swapchain_dims);
   rp_info.renderArea.extent.height = VIDEO_SCALE_H(vk->context->swapchain_dims);

so the two have to be the same size, and nothing kept them so. The
swapchain is sized from the window's surface whenever it is built. The
off-screen buffer and the readback image were sized from what the
frontend last reported, and only at the end of a frame that had been
told to resize. A swapchain rebuilt by another road - a present that
comes back out of date destroys it, and the acquire that follows makes
a new one at the surface's size - left the old buffers standing. The
frames after that asked for a render area the size of the new
swapchain on a framebuffer the size of the old one, which Vulkan does
not allow.

Dragging a window's edge, the two differ by a few pixels for a frame
and nothing was seen to come of it. A window taken from 960x720 to
3840x2160 in one step (35f2d183d's in-place fullscreen toggle) is
another matter. The log from an RTX 5090 with an HDR10 swapchain:

  [Vulkan] QueuePresent failed (err = -1000001004, result = -1000001004), destroying swapchain.
  [Vulkan] Got 3 swapchain images.
  [Vulkan] The device was lost (VK_ERROR_DEVICE_LOST).

and what reached the screen on the way was the picture at the old
window's size in a corner of the new one: the old buffer, composited.

vulkan_hdr_buffers_init() makes both at the swapchain's size, and is
called wherever they are made: when the driver starts, at the end of a
frame that resizes, and - new - in vulkan_check_swapchain(), which
runs whenever the swapchain's images have changed and already rebuilds
everything else that depends on them.

Compiled for Windows with mingw with the HDR code in. Not run: there
is no HDR swapchain to be had on the software driver here, so this
rests on the code and on the log above.
2026-10-03 04:13:51 +02:00
LibretroAdmin a4fce4ee7b os/gekko: audio DMA and GameCube controllers
gk_audio_* plays 16-bit stereo PCM by DMA at 48 or 32 kHz: the
interrupt at the start of each buffer runs a callback that queues the
next one, which the DMA picks up when the current one ends.

gk_pad_* finds controllers on the serial interface (identify, then the
origin they report for their sticks), polls them once a field and
decodes each report in the interrupt; ports are rescanned every half
second for controllers plugged in later, and rumble rides on the poll.

os/gekko/tests/io_test checks the buffer rate at both sample rates and
the controller Dolphin puts on port 1; run-dolphin.sh makes that
controller count as connected.
2026-10-03 04:12:29 +02:00
libretroadmin 80272e2e12 glsl: the menu ribbon draws from the buffer that already holds it
The ribbon pipelines draw the one grid the menu driver built, handed
back every frame as their only attribute. The GLSL backend keeps the
array it last uploaded for them - its address, length and first and
last floats - and when the same array comes back binds the buffer as
it is, without copying the 8064 vertices into the scratch and comparing
them against the last upload, 64 KiB each, on every frame. Any other
draw on those pipelines, or a rebuilt array, goes the usual way.
2026-10-03 03:56:36 +02:00
LibretroAdmin 1a6ad04e61 os/gekko: graphics processor
gk_gx_* drives the GPU: a command ring the write-gather pipe fills,
with the writer put to sleep on the high-water mark and woken on the
low one, so any amount of commands is safe; draw-done waits on the
pixel engine's finish interrupt; vertex descriptors, formats and
arrays; position matrices, orthographic and perspective projection,
viewport with field jitter and scissor; textures; TEV stages for
modulate, replace and vertex colour; culling, depth, blending; EFB
clears and copies to an XFB, vertically scaled when the line counts
differ; EFB pokes; display lists recorded through the same pipe.

os/gekko/tests/gx_test draws each case in Dolphin's software renderer
and checks the pixels it reads back from the XFB.
2026-10-03 03:56:09 +02:00
LibretroAdmin 8083cb12a3 win32/vulkan: the in-place fullscreen toggle is switched off for now
35f2d183d let a borderless fullscreen toggle restyle the window it
has, with the Vulkan driver seeing a resize. On the machine it was
written for the window does what it should, and then:

  [Vulkan] QueuePresent failed (err = -1000001004, result = -1000001004), destroying swapchain.
  [Vulkan] Got 3 swapchain images.
  [Vulkan] The device was lost (VK_ERROR_DEVICE_LOST).
  [Video] The GPU device was lost; reinitialising the video driver.

The present after the resize is out of date, as it should be; the
swapchain is rebuilt; and the next frame's vkQueueSubmit answers
VK_ERROR_DEVICE_LOST. The restart that follows goes the same way two
more times before it holds. Until the swapchain is rebuilt the old
one is shown at its old size in the fullscreen window, which is the
small picture seen on the way.

This is the third form of one thing on that setup (NVIDIA, HDR10
swapchain, threaded video): a swapchain rebuilt on a live window is
followed by a lost device or a dead picture. A toggle that restarts
the drivers builds its swapchain once and works there, so the Windows
Vulkan context stops offering GFX_CTX_FLAGS_FULLSCREEN_IN_PLACE and
the toggle goes back to the restart.

win32_window_set_fullscreen(), its use in the context's
set_video_mode, and the test of it on a real window all stay; only
the flag goes, and comes back when a rebuild holds.
2026-10-03 03:50:30 +02:00
LibretroAdmin 35f2d183dc win32/vulkan: a borderless fullscreen toggle keeps its window and its drivers
Toggling fullscreen restarted everything. The log of one F press on
Windows with Vulkan: the video thread stopped and started, the device
and the shader chain rebuilt, the audio driver closed and reopened its
pin, the input and joypad drivers freed and started, the controller
detected and configured again, and the window destroyed and created.
For exclusive fullscreen the window has to go, since the display mode
changes under it. For borderless fullscreen - Windowed Fullscreen,
the default - the only thing that differs between the two states is
the window's style, position and size.

win32_window_set_fullscreen() changes those on the window that is
there: the style, the menu bar (which belongs to the windowed state),
and a position and size worked out the way a new window's are. Going
windowed, the window returns to where it stood before it went
fullscreen, to the saved position if positions are saved, or to the
middle of its monitor if it has never been windowed. The video driver
hears of it as it hears of any resize, through WM_SIZE, and rebuilds
its swapchain for the new size. Exclusive fullscreen is refused and
left to the restart.

It runs through the path that already exists for changing the video
mode: the toggle calls video_driver_set_video_mode(), the threaded
video wrapper carries that to the thread that owns the window, and
the Windows Vulkan context, finding a window already up, restyles it
instead of creating another. A context says it can do this with
GFX_CTX_FLAGS_FULLSCREEN_IN_PLACE; the toggle uses it when Windowed
Fullscreen is on and the context has the flag, and restarts the
drivers as before otherwise. Only the Windows Vulkan context has it
so far.

The size a window is asked for was worked out inside
video_driver_init_internal(); that is video_driver_window_size() now,
so the toggle asks for the size a restart would have.

samples/gfx/win32_window_proc drives it on a real window under Wine:
twenty toggles on one window, which must stay the same window, wear
the right style, be the monitor's size when fullscreen and the asked
size when windowed, and raise a resize each time; and exclusive
fullscreen must be refused. The stand-in video state gets a core
geometry for this - without one the window code limits a window to a
width too small to hold it - and the recorded trace of the old window
procedures is unchanged by that.

Compiled for Windows with mingw (win32_common.c, the Vulkan context
and driver, video_driver.c, retroarch.c). Not run on Windows, and the
Wine test has no GPU in it: what the Vulkan driver does when its
window changes size under it this way is the same code as a resize by
hand, but has not been seen with this.
2026-10-03 03:45:38 +02:00
LibretroAdmin 8aacd31c22 os/gekko: video interface, A/V encoder, console and crash screen
gk_vi_* programs the video interface from one base timing per
standard (NTSC, PAL, MPAL, EURGB60) and scan (interlaced, 480p, double
strike): a mode smaller than the full picture moves the blanking edges
and keeps the line and field lengths, framebuffers narrower than the
shown width use the horizontal scaler, and black keeps the timing.  A
retrace interrupt per field takes pending framebuffers and runs a
callback.  On the Wii the A/V encoder gets the colour encoding, YUV
output for component cables and the EURGB60 filter over its I2C lines.

Debug output also goes to a text console drawn with RetroArch's bitmap
font; a crash shows its report there and returns to the loader after
ten seconds.

os/gekko/tests/vi_test checks every standard and scan type retraces at
its broadcast rate.
2026-10-03 03:38:24 +02:00
libretroadmin 882aad01a1 Core updater: leave out uninstalled cores before building their paths
The installed-only list parse looks each listing line up in its walk of
the libretro directory once the date and CRC have parsed, and leaves out
a core that is not installed before building its paths - a URL-encode,
a realpath and four allocations - rather than after. A libretro
directory that could not be walked still falls back to a stat of the
built path. Update Installed Cores' parse of a 242-entry listing with 40
installed cores drops from 1.2 ms to 0.8 ms warm.
2026-10-03 03:27:02 +02:00
LibretroAdmin 3f618f9c76 companion_thumbs: hand animation frames over without a lock
The animation thread publishes each frame through a triple buffer
that poll() takes the newest from. Workers and the animation thread
sleep on eventcounts, and quit, the epoch, the request generation and
the counts poll() and pending() read are atomics. The preview session
the UI thread feeds audio from is held with a user count, and the
animation thread waits the holders out before closing it. The lock
now covers only the job rings, the done list and the parked session.

The queued count is the rings' length, so pending() reaches zero once
the rings drain, also after a full ring has dropped requests.

samples/ui/companion_thumbs_handoff drives the engine against fake
decoders: worker drain and cancel, animation switching, audio only on
live sessions, the still-to-animation session handoff, an overfull
queue, and free with work in flight. It runs in the TSan lane.
2026-10-03 03:25:44 +02:00
LibretroAdmin 0b8bfa2595 win32: one window procedure for every video and input driver, with a test on a real window
This is 3a45e3181 again, which was reverted in dae95f4cc, with the
test it should have come with.

It was reverted because a fullscreen toggle froze on Windows right
after it went in. That turned out to be something else - a driver
restart with the menu up switched vsync on whatever the setting, and
the swapchain rebuilt for it went wrong on one Vulkan setup (fixed in
eb33c4d2e) - and the freeze was there with this reverted. But when it
was reverted nothing could show that: the test it had,
samples/gfx/win32_msg_route, compares the new routing with a
transcription of the old switches, and not with the old code running.

samples/gfx/win32_window_proc does that. It is built with mingw-w64
and run under Wine: the real gfx/common/win32_common.c, linked with
stand-ins for everything it calls outside itself, each of which
records the call. For every video family (Direct3D, WGL, Vulkan, GDI)
and every kind of input driver (DirectInput, raw input, other) the
window code registers its class and creates its window, and the test
sends it 41 messages - keys and system keys, the mouse, focus, the
hotplug timer, the move and menu loops, size, device and display
changes, IME, close - once with the input driver's handler passing on
what it is offered and once with it taking it. Every answer and every
call made on the way goes into a trace. Then twenty fullscreen
toggles, windowed and borderless in turn, the window made and
destroyed each time.

expected_trace.txt is that trace from the twelve old procedures: the
test built against dae95f4cc. The one procedure gives the same 2695
lines. A procedure changed to make keyboard events from raw input's
key messages, which is what routing them as another driver's would
do, differs from it in 104.

What the trace cannot see is what Wine does not do as Windows does,
and nothing here involves a GPU driver.

The change itself is as before:

gfx/common/win32_common.c exported twelve window procedures, one for
each video family times each kind of input driver, built on three
copies of one long switch. Each video driver chose its three by
comparing the input driver's name. There is one procedure now,
win32_window_proc(). What it does with a message is decided in
gfx/common/win32_msg_route.h; a video driver says which family its
window is with win32_window_proc_setup() before creating it; and the
kind of input driver is read from the setting in that one function.
Nine video drivers and contexts lose their comparison of the input
driver's name, and win32_common.c goes from 2904 lines to 2602.
2026-10-03 03:21:07 +02:00
libretroadmin a25626afae shaders: the drivers keep compiled shaders through a cache store
What a shader driver keeps between runs - the SPIR-V the slang compiler
produced, a PS4 shader binary - is stored by key through
video_shader_cache_map(), video_shader_cache_unmap() and
video_shader_cache_write() in video_shader_parse.c, which decide where
it lives, map an entry where the platform maps files and read it in one
go where it does not, and write one whole beside the old and rename it
into place. A key is letters and digits, never a path. slang_cache.c
lays an entry out in one buffer and parses it where it lies, in the
layout caches have always had, with the same bounds; shader_glsl.c asks
for the PS4 binary by its hash. Nothing under gfx/drivers_shader opens
a file or builds a path.

samples/gfx/slang_cache runs the cache against a real directory, mapped
and reading: an entry round-trips in the old layout, and every
truncated prefix, a wrong version, oversized counts, an over-long
string and a key naming anything outside the cache are turned away.
2026-10-03 03:17:11 +02:00
LibretroAdmin d61b027ace os/gekko: IOS requests, buttons, power and console settings on the Wii
gk_ios_* talks to IOS through the IPC mailbox: requests queue behind
the one in flight, complete by waking the caller or by a callback in
interrupt context, and handle the cache for the buffers they carry.
On top of it: the power and reset buttons (STM event hook on the Wii,
the reset switch interrupt on the GameCube), power off and restart,
leaving to a resident loader or else the system menu, and the console
settings RetroArch reads (aspect, progressive scan, PAL60, video mode,
horizontal offset) from SYSCONF and setting.txt.

os/gekko/tests/ios_test runs in Dolphin like the kernel test.
2026-10-03 03:16:05 +02:00
libretroadmin de4cf0ef5f net_http: drain the socket within the caller's time window
net_http_update_budget() drains the socket while a caller-supplied
window allows, one read - with the TLS decryption behind it, and still
at most NET_HTTP_DRAIN_BUDGET (256 KiB) - per work item, instead of up
to a fixed 256 KiB per call; net_http_update() is that call without a
window and drains exactly as before. task_http.c passes the shared
per-frame I/O window, so a transfer moves as much a frame as its share
of the window allows on any link and any cipher speed: with the task
queue unthreaded, ten core updates totalling 180 MB download in 1.8 s
instead of 30.6 s; threaded transfers are unchanged.

http_transfer_stress_test gains a window lane: a window of one item per
update gets exactly one read per update, an unbounded one clamps every
read to 256 KiB and drains in fewer updates, and the body arrives intact
both ways. The http samples linking task_http.c now link
task_nbio_slice.c.
2026-10-03 03:03:25 +02:00
LibretroAdmin 08036b1a04 dbus: the screensaver worker's request and answer take no lock
The request is one atomic word - open, want, the connection's
generation and a sequence that moves on every change - which a caller
on any thread moves on with a compare-and-swap and an eventcount
notify. The worker waits on the eventcount for the word to change and
works through the latest it finds. Its answer is stored tagged with
the request it answers, so one the request has moved past reads as
pending, never as the answer to the new request.

samples/gfx/dbus_screensaver runs it against a private session bus and
a fake org.freedesktop.ScreenSaver, under ASan and TSan.
2026-10-03 03:02:40 +02:00
LibretroAdmin fae4c58ae1 os/gekko: boot, exceptions, scheduler and C library for GameCube and Wii
The start of RetroArch's own system layer for the two consoles.  crt0
sets up the processor and memory map; exceptions save into the running
thread and leave through one scheduler decision; threads run by strict
priority with round robin among equals; every blocking primitive sleeps
on a futex word, so newlib's locks are the kernel's mutexes and need no
setup; FP registers move lazily, interrupt handlers included.  newlib's
heap (MEM1, then MEM2), pthreads, thread-local storage and clocks go
through the kernel.

os/gekko/tests holds checks that run in Dolphin:
make -C os/gekko/tests [PLATFORM=dol], then run-dolphin.sh.
2026-10-03 03:01:58 +02:00
LibretroAdmin 100418ee62 CI: the heaviest Linux lanes that build RetroArch from scratch use ccache
A third of what one push costs in runner time is compiling RetroArch,
and most of the lanes that do it start from nothing every time.
Fifteen workflows already keep a ccache between runs. These five are the Linux
lanes that spend the most on a build and had none:

- Linux-vulkan-validation: the validation, ASan and TSan harness jobs
  and the six gl/glcore lanes
- Linux-asan-ubsan
- Linux-Headless
- Linux-library-net-backends: all three lanes
- Linux-autoconf-tests: all three lanes

About 33 runner-minutes of compiling per push between them.

The steps are the ones the samples workflows use, after checkout: the
cache is restored from the job's last run and /usr/lib/ccache goes on
PATH, so configure and make pick it up with no change to either. A
matrix job keys its cache by lane; the lanes build with different
flags and would otherwise overwrite one another's.

Every lane still runs on every push and builds what it built before.

Measured here with the autoconf lane's configure line: 288 seconds
cold, 14 seconds three commits later, 375 of 376 objects from the
cache, 12 MB of cache. Not run on GitHub's runners.
2026-10-03 02:59:03 +02:00
LibretroAdmin eb33c4d2e5 reinit: the menu gets vsync after a restart only if vsync is on
When the menu comes up, menu_driver_toggle() gives it vsync "if vsync
is on": it checks the setting first. The end of command_event_reinit()
did the same thing without the check. With vsync off, every driver
restart with the menu up - a fullscreen toggle, a driver or HDR
setting - started the new video driver without vsync, as configured,
and a few frames later switched it on. So the menu ran with a vsync
the user had turned off, where at startup it does not, and the
swapchain the driver had just made was thrown away and made again.

That second swapchain is where a fullscreen toggle went wrong on one
Windows machine (Vulkan, NVIDIA, HDR10, threaded video, vsync off).
Its logs show, each time, a new driver creating its swapchain with
present mode IMMEDIATE, then "Creating swapchain with present mode:
FIFO" right after the input drivers are back, and then either

  [Vulkan] QueuePresent failed (err = -4, result = -4), destroying swapchain.

or nothing more at all, with the run loop and the video thread still
running and the picture no longer changing. At startup, where the
swapchain is made once, the same fullscreen window works.

The check is the one menu_driver_toggle() has: vsync on, scanline sync
off. With vsync on the call is made as before, and asks for the
interval the driver already started with.

Why the driver there answers the rebuilt swapchain the way it does is
not known, and a user with vsync on who changes the swap interval
still rebuilds it. Built here and compiled for Windows with mingw; the
fullscreen toggle itself has not been run on Windows with this.
2026-10-03 02:58:58 +02:00
LibretroAdmin 2ff3e7b337 vulkan: one failed present is not a lost device
On Windows, with an NVIDIA card and the menu up, a fullscreen toggle
could bounce between windowed and fullscreen two or three times by
itself and then leave a dead picture.

The log of it:

  [Vulkan] Creating swapchain with present mode: FIFO.
  [Vulkan] QueuePresent failed (err = -4, result = -4), destroying swapchain.
  [Vulkan] Got 3 swapchain images.
  [Video] The GPU device was lost; reinitialising the video driver.
  ...
  [Video] The GPU device was lost again (2 in a row); next rebuild in 1000 ms.
  [Video] The GPU device was lost again (3 in a row); next rebuild in 2000 ms.
  [Video] The GPU device was lost again (4 in a row); next rebuild in 4000 ms.

-4 is VK_ERROR_DEVICE_LOST. A toggle restarts the video driver; with
the menu up the swap interval is set right after, which rebuilds the
swapchain a few frames into the new driver; and the first present
after that rebuild was answered with VK_ERROR_DEVICE_LOST. The device
was not gone: it built the next swapchain and its images straight
away, as the log shows. vulkan_present() raised
VIDEO_FLAG_GPU_DEVICE_LOST all the same, and the run loop rebuilt the
whole video driver - window, device, input drivers - which is the
bounce. The new device was answered the same way, so it happened
again, each retry later than the last, and after six in a minute the
run loop gives up for a minute with the picture dead, which is the
freeze.

A present that fails with VK_ERROR_DEVICE_LOST now costs a swapchain,
as any failed present does, and the next frame presents on a new one.
The device is treated as lost when presents have failed on eight
swapchains in a row, or when the frame's own vkQueueSubmit says so
(vulkan_check_device_lost), which a device that is really gone does
on the next frame.

Why that one present is answered this way is not known. What is known
is from one machine's log: an RTX 5090, an HDR10 swapchain, threaded
video. Compiled for Windows with mingw; not run on Windows, and not
tried against the validation sample here, which needs a Vulkan build.
2026-10-03 02:52:30 +02:00
LibretroAdmin 94f646eb86 font_layout: no set-but-unused warning where a driver ignores a line's width
font_layout.h measures each line - its width and glyph count - and
hands both to the including driver's FONT_LAYOUT_LINE. A driver that
draws left-aligned needs neither: SDL2's OSD takes only the line
number. There the two are added to and never read, which newer GCC
reports under -Wunused-but-set-variable:

  gfx/drivers/../font_layout.h:86:11: warning: variable 'fl_count' set
  but not used
  gfx/drivers/../font_layout.h:85:11: warning: variable 'fl_width' set
  but not used

Read both once after the macro. No change to what is drawn.

The warning is new with the leveled -Wunused-but-set-variable of
recent GCC and does not show with GCC 13, which is what this was
compiled with: sdl2_gfx.c and gdi_gfx.c build as before, and the
warning itself going away has not been seen here.
2026-10-03 02:42:35 +02:00
LibretroAdmin dae95f4cc6 Revert "win32: one window procedure for every video and input driver"
This reverts commit 3a45e31811.

After it, on Windows with Vulkan, threaded video and the raw input
driver, toggling fullscreen with F and then using the menu left the
picture frozen: the run loop kept running, the menu received input and
was drawn, and Vulkan presented every frame without an error, but the
image on screen no longer changed. It did not happen before this
commit.

The cause inside the commit has not been found. The test that came
with it compares the new message routing with a transcription of the
three old switches, not with the old code running on Windows, so it
cannot show what differs on a real window. The twelve procedures come
back until that is understood and tested against a real window.
2026-10-03 02:40:25 +02:00
libretroadmin a812e7fb44 gfx: on-screen text from the glyph cache on OpenVG, SDL2's OSD and GX
OpenVG draws on-screen messages again, through the shared glyph cache
and gfx/font_layout.h: the A8 atlas is one VGImage, uploaded where it
is dirty and made again when it grows, and each glyph is a child image
of its rectangle drawn multiplied by the message colour, drop shadow,
alignment, line spacing and scale as the other drivers draw them.
SDL2's own OSD message walks the text through the same layout and
remakes its texture when the atlas has glyphs it does not, and GX
decodes the message as UTF-8 into its bitmap font, drawing what that
font lacks as '?'. The glyph cache reads its frame counter relaxed, a
plain load on every backend; it is only compared.

The OpenVG stub carries the Khronos prototypes and values, so the
compile matrix checks vg.c's calls, and a GameCube lane builds GX's
SRAM branch. samples/gfx/font_lifecycle gains vg and sdl2: the
shipping drivers draw messages, OpenVG onto a software OpenVG and SDL2
through its software renderer, and every glyph texel is checked where
it lands.
2026-10-03 02:39:23 +02:00
libretroadmin 1ba8fb2ea4 Core backup: pace backups and restores with the shared I/O window
Every CRC step and transfer quantum of a core backup or restore is a
work item of the shared per-frame I/O window (task_nbio_slice_*) rather
than of a 2 ms deadline of its own, so a backup shares one frame's I/O
allowance with the other budgeted tasks - the core updater, extraction,
the HTTP transfer - instead of spending it a second time. The window's
floor still moves one item per tick however slow the device.

core_backup_io_test gains a shared-window lane: a task pushed ahead of a
backup spends the whole window every tick, and the backup must then do
exactly one read per tick and still complete.
2026-10-03 02:22:37 +02:00
LibretroAdmin 8b0f5fec2a android_lifecycle_flush_test: the acknowledgement is the driver's own
The harness reproduces the command handling around the background
flush, and now acknowledges and waits through
frontend/drivers/android_lifecycle.h, as the driver does, instead of a
mutex and condvar copy of the protocol the driver no longer uses.
2026-10-03 02:17:56 +02:00
LibretroAdmin 94c38ff94d android: drop the native_app_glue fields nothing uses
userData, onAppCmd, onInputEvent and the two poll sources came with
the glue and were never set or read.
2026-10-03 02:15:36 +02:00
LibretroAdmin aa5e71c3ba android: the activity lifecycle handshakes take no lock
The NativeActivity callbacks wait on the app thread through
frontend/drivers/android_lifecycle.h: a completion count, the
acknowledged activity state and a few flags, each an atomic the app
thread stores before notifying one eventcount every waiter re-tests
its predicate on. The mutex and condvar go.

A window or input queue travels with its command through the pipe, so
nothing is shared to hand it over. The window is an atomic pointer;
the video context holds it across setBuffersGeometry() with
window_acquire/window_release, and APP_CMD_TERM_WINDOW retires it,
waiting out any holder, before the UI thread may hand it back.

samples/frontend/android_lifecycle runs the handshakes with a UI
thread, an app thread and a window holder, and is in the TSan list.
2026-10-03 02:15:31 +02:00
LibretroAdmin 6f0efe046c test_joypad: a script can end the run, and the autoconfig suite's do
No run in tests-other/test_autoconf.sh ended on its own. The test
joypad driver played its script and RetroArch then idled until
timeout(1) killed it, 25 seconds after it started: seventeen waits,
about seven minutes a lane, for a suite whose scripts are over in
well under a second. Pushes to master arrive faster than that, and
most of the job's recent runs were cancelled before they reported.

The driver has a quit step (action 4). It is taken once every connect
and disconnect the steps before it started has been applied, so what
the script did is in the log before the run ends, and it leaves by
CMD_EVENT_QUIT like the menu's Quit.

The five scripts the suite plays end in one. timeout(1) stays as the
bound on a run that hangs, and a run that needs it now fails the suite
rather than passing 25 seconds late.

tests-other/test_autoconf.sh, one CPU: 7 minutes before, 2 seconds
now, ten runs in a row all pass; 4 seconds and clean under ASan +
UBSan. TSan not run here.
2026-10-03 02:08:08 +02:00
LibretroAdmin 1863583edb gx: internal libogc keeps newlib's stdio and library locks
The syscall bridge makes each lock's lwp mutex on first use, as
devkitPPC after r38 zero-initialises locks, and serves the recursive
hooks stdio takes, so a FILE one thread holds blocks the others.
lock_init passes the recursive flag through.
2026-10-03 02:07:15 +02:00
LibretroAdmin 3a45e31811 win32: one window procedure for every video and input driver
gfx/common/win32_common.c exported twelve window procedures: one for
each video family - Direct3D, WGL, Vulkan, GDI - times each kind of
input driver - DirectInput, raw input, any other. Each video driver
chose its three by comparing the input driver's name. Underneath were
three procedures that were the same long switch, copied, with a few
cases different in each.

There is one procedure now, win32_window_proc().

- What it does with a message is decided in one place,
  gfx/common/win32_msg_route.h: for a message and the kind of input
  driver in use, the steps to take - an IME step, a key step, the
  cursor clip, an offer to the input driver, the window's own
  business, a display change - which the procedure carries out in
  that order. The three switches are gone; what each did is the route
  for its kind.
- A video family differs from another only in what creating the
  window sets up, and GDI in painting from the procedure. A video
  driver says which family its window is with
  win32_window_proc_setup() before creating it.
- The kind of input driver is read from the setting in that one
  function, at the moment each video driver used to read it. Nine
  video drivers and contexts lose their comparison of the input
  driver's name.

Behaviour is meant to be unchanged, message for message. The header
takes no Windows types, and samples/gfx/win32_msg_route holds it to
the three switches, which are written out in the test as they stood,
case list for case list and #if for #if: for every message, with and
without the hotplug timer's id in wparam, on the desktop and on the
Xbox, with and without threads, the route is the steps the old switch
took. The message numbers the header spells out are checked against
the SDK's when win32_common.c is compiled.

One oddity is kept on purpose, so that the comparison is exact: with
an input driver, the hotplug timer's id is looked for under the timer
message and also under the eight messages the switches happened to
list before it.

Compiled for Windows with mingw: win32_common.c with and without the
two input drivers and with every family, the Vulkan and WGL contexts,
and the GDI, Direct3D 8, 9 (HLSL) and 10 drivers. The Direct3D 11 and
12 drivers do not build against this mingw's headers for reasons that
have nothing to do with this change, and the Cg driver needs Cg; their
edit is the same three lines. Not run on Windows.

win32_common.c goes from 2904 lines to 2602.
2026-10-03 02:00:36 +02:00
LibretroAdmin 34af7cf2d5 gx: internal libogc links on current devkitPPC
The syscall bridge supplies _sbrk_r, so the heap stays on libogc's
MEM1/MEM2 arenas whichever libsysbase is linked, and the ctype accessor
the prebuilt Wii ipc.o calls when newlib exports only the macro.
Makefile.wii links the bridge as Makefile.ngc does.

The GX linker scripts pin _SDA2_BASE_ to the start of .sdata2, keeping
the prebuilt libbte's small-data references in range.
2026-10-03 01:56:17 +02:00
LibretroAdmin 71ecf61126 file_path: path_mkdir creates a directory directly under a device root
A directory such as ms0:/x or host:x is made with one mkdir of the path
itself; its device root is never probed, as the PSP firmware refuses a
stat of ms0:/.

path_mkdir_test pins the device-root, chain and unaffected cases.
2026-10-03 01:56:13 +02:00
LibretroAdmin bf29c229f5 input: the snapshot bridge stops copying controls nobody reads
A pad's copy held every axis and hat a reader had ever asked for,
until the next controller was reported in the slot. The bind screen
scans all sixteen axes, so one visit left that pad copying sixteen
axes a frame - thirty-two calls into the driver - for the rest of the
session, and a remap that dropped a stick kept it in the copy.

The copy now holds what was read out of the last one. A control that
has stopped being read leaves a frame later; one read again is fetched
from the driver that once and is in the next copy, as before.

samples/runloop/frontend_overhead reads every axis through the bridge
for twenty frames and then stops: the driver must be called as often
afterwards as before. Without this change it is called 1520 times in
twenty frames after the scan against 1040 before it.

With the RawInput and XInput drivers now read through the bridge this
is what a Windows user would have got after rebinding a control.
2026-10-03 01:51:46 +02:00
libretroadmin 90d78c23b0 cloud sync: the unload sync starts once the save RAM is on disk
With Cloud Sync in automatic mode, closing content asks for a sync.
When content is running, CMD_EVENT_UNLOAD_CORE now only marks it
(RARCH_FLAGS_CLOUD_SYNC_ON_DEINIT), and retroarch_main_deinit_finish
pushes it: by then the content's save RAM has been flushed and no
core owns the save file, so the sync uploads and hashes the save the
content left behind, and may update it from the server. With no
content running the sync is pushed at once, as before. On the way out
the task queue has already been drained, so a sync marked there is
dropped, as one pushed before the drain was cancelled.

samples/runloop/cloudsync_unload links the shipping retroarch.c with
task_push_cloud_sync wrapped, loads a core with save RAM, closes the
content from a frame loop and checks what the save file held when the
sync was asked for. It runs in the menu harness workflow.
2026-10-03 01:50:55 +02:00
libretroadmin 4d8081e3c0 vfs: retro_vfs_rmdir_impl() for UWP
The UWP build compiles vfs_implementation_uwp.cpp in place of
vfs_implementation.c, so it needs its own retro_vfs_rmdir_impl() for
path_rmdir() to link: RemoveDirectoryFromAppW(), beside the
DeleteFileFromAppW() its file removal already uses.
2026-10-03 01:50:38 +02:00
LibretroAdmin aaf3ac58da test_joypad: Save Controller Profile waits for the controller to be there
The test joypad driver runs its script on a frame count. A controller
it adds is only there once its autoconfig task has been applied on the
main thread, and frames do not wait for that: with the null video
driver they are not paced at all. A save step a fixed number of frames
after an add could run first, find no device on the port and report
"failed".

That is the autoconfig suite's P10 failing in CI, "port mapped to
another device index saves": the second pad is added at frame 30 and
saved at frame 120, and on a busy runner 90 unpaced frames go by before
the task thread has scanned the profile directory. On one CPU it fails
every time.

The save step now stays due while any autoconfig connect or disconnect
has yet to be applied, and the steps behind it wait their turn, so the
script keeps its order. task_queue_find() reports a task until its
callback has run, so there is no window between the scan finishing and
the port being written. The menu cannot save a controller that is not
there yet either, so this is what the step was meant to do.

input_autoconfigure_pending() is built only with HAVE_TEST_DRIVERS.

tests-other/test_autoconf.sh, one CPU: P10 mapped save failed before,
passes now; whole suite passes.
2026-10-03 01:46:42 +02:00
LibretroAdmin 80708d2083 input: the RawInput joypad driver is read through the snapshot bridge
winraw_joypad is the joypad driver a Windows build picks with the raw
input driver, and the second switched over to the bridge. It already
has what the bridge needs: get_buttons() is button() for every plain
button, hats and axes answer through button() and axis(), and state()
is the common loop over the binds with the reads written out in place.
It compares an axis with the threshold as 32-bit integers, which
agrees with the common loop at every threshold, 1.0 included.

One thing in its state() was wrong, and is fixed here because the
bridge does not have it and the two should not differ. The pad to read
is the one the player's Device Index names. state() looked first at
the slot with the player's own number and returned nothing if that was
empty, before turning to the Device Index. A player mapped to another
slot - by hand, by a reserved device, or put back there after a driver
restart - had no buttons whenever the slot of their own number held no
pad, while button() and axis(), which the hotkeys and the sticks go
through, read the right pad.

RETROARCH_INPUT_SNAPSHOT=0 in the environment reads the driver
directly, as for xinput, and the log says when the bridge is in use.

Compiled for Windows with mingw. Not run on Windows.
2026-10-03 01:42:18 +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
LibretroAdmin bfe9320094 input: the Windows XInput driver is read through the snapshot bridge
The first driver switched over. xinput_hybrid_joypad.c - the Windows
joypad driver, with its XInput and its DirectInput pads - is now read
through a copy of each pad taken once a poll, not by a call into the
driver for every button and axis a reader wants.

What the bridge needs of a driver, checked here by reading it:

- get_buttons(). The driver had none; it gets one. Bit i is what
  button(port, i) answers: the XInput button word through the driver's
  own index table, or a DirectInput pad's button array.
- Hats and axes through button() and axis() alone. An XInput pad's
  d-pad answers to a hat key on any hat index and a DirectInput pad's
  POVs to their own; sticks give each direction separately and
  triggers only the positive one, which is what the copy is rebuilt
  from.
- state() being the common loop over the binds. It is, with one
  difference: the driver compares an axis with the threshold as
  integers where the common loop divides. Over every axis value and
  every threshold from 0.000 to 0.999 the two agree. At a threshold of
  exactly 1.0 the driver's own wraps negative and reads every bound
  axis as pressed; through the bridge it reads none, which is what a
  threshold of 1.0 says.

The XInput-only driver of the same name (xinput_joypad.c, for builds
without DirectInput) has no get_buttons() and is read as before.

The log says when the bridge is in use. While drivers are being
switched over, RETROARCH_INPUT_SNAPSHOT=0 in the environment reads the
driver directly, so a problem can be tried both ways on one build.

Compiled for Windows with mingw (the driver and input_driver.c) and
through the Android unity build. Not run on Windows: this needs a pass
on hardware - an XInput pad's buttons, d-pad, sticks and triggers in
the menu and in a core, a DirectInput pad through the same driver,
hotkeys held on the pad, and plugging a pad in and out.
2026-10-03 01:33:41 +02:00
LibretroAdmin d66bae592d rinflate: keep back-reference history when an output buffer stays bound
The ring window now holds only output not yet committed, out[wcur..
out_pos), appended at the end of each process() call, and a
back-reference reaching before out[0] is resolved against the ring
ending at out[wcur]. A caller that keeps one output buffer bound across
several calls and rebinds it when it fills - the ZIP backend streaming
a member to disk through its 256 KiB window - gets correct output; such
members failed their CRC on builds using the built-in inflater, which
is how Update Slang Shaders fails with "Decompression failed".

rinflate_window_test decodes a stream full of matches through a window
bound once and rebound on fill, through per-call slices, and after a
dictionary. decompress_window_test's large member puts an input slice
edge inside an output window, so its stream lane under
BUILTIN_DEFLATE=1 fails on a decoder that loses that history.
2026-10-03 01:29:46 +02:00
LibretroAdmin c54be842d0 input: a snapshot bridge between readers and the joypad driver, off for now
Everything that reads a controller - a core's state query, the
hotkeys, the menu - calls into the joypad driver for it: a button
here, an axis there, at whatever point in the frame the reader gets to
it. A driver whose state is filled in by a thread of its own can
change between two of those calls, so a reader can see half of one
report and half of the next. And no reader can leave the frame's
thread while it has to call the driver.

The bridge puts a copy between the two. The first read of a pad after
a poll copies the pad out of the driver in one go: its buttons, and
the axes and hats readers have asked for before. Every read after
that, until the next poll, comes from the copy. A control nobody has
asked for yet is fetched from the driver the first time and is part
of the copy from then on, so what is copied settles on what the binds
use without anyone having to say when binds change. What is known of
a pad is forgotten when a new controller is reported in its slot.

To readers it is a joypad driver: the real driver's table with button,
axis, state and get_buttons replaced, handed out where the idle driver
already is for an unfocused window. Its state() is the loop every
driver's state() is, run against the copy. Rumble, sensors, names and
the poll itself still go to the real driver. The pads copied this
frame are two more words in frame_valid, cleared with the rest.

It is off. A driver is switched over when it has been checked - that
its get_buttons() says what its button() says and its state() is the
common loop - and tried on hardware; none has been, so the list of
checked drivers is empty and shipping builds read their drivers as
before. input_driver_set_snapshot_bridge() switches it on regardless
of driver, for the harness.

This is the legacy bridge of the input plan (WP-03, WP-05).

samples/runloop/frontend_overhead holds it to the reference. The
core-view lane runs every mapping twice, reading the driver and
reading through the bridge, and both must give the recorded digests:
the default mapping, a remap, analog-to-d-pad, the four turbo modes,
hold, run-ahead, and a new one with the d-pad on a hat, which nothing
covered. Through the bridge the driver's state() must not be called
at all. With the harness's eight pads connected the driver is called
342 times a frame read directly and 46 times through the bridge.
HARNESS_SNAPSHOT=1 runs every other lane through it as well; they
pass, and the frame and the input lanes cost the same either way.

Known and left: a control stays in a pad's copy once asked for, so
the bind-capture screen, which scans every axis, leaves that pad
copying all of them until it is next reported.
2026-10-03 01:25:37 +02:00
LibretroAdmin fdc7bec6cc runahead: give the core back its own poll callback
To run a frame on the input it already has, run-ahead swaps the core's
poll and state callbacks for ones that replay, runs it, and puts them
back. What it put back as the poll callback was cbs->poll_cb. That is
not what the core had: it is the frontend's own "poll now",
input_driver_poll(). The core is given core_input_state_poll_maybe()
at load, which polls only when the poll mode says the core's call is
the one that does.

So from the first frame of run-ahead on, the core's input_poll polled
unconditionally. Under late polling, the default, the frame's first
input_state polls as well; under early polling the frame has polled
before the core runs. Either way every frame polled twice - and went
on doing so after run-ahead was switched off, until the core was
reloaded.

Turbo counts polls. With run-ahead in use, or used once since the
core was loaded, turbo fired at twice its configured rate.

Restore runloop_st->input_poll_callback_original, as the preemptive
frames teardown already does.

samples/runloop/frontend_overhead found this. Its core-view lane ran
the turbo mappings after the run-ahead one and got different buttons
than before it. The lane now checks, for every mapping, that input was
polled once a frame; without this change it reports 2400 polls in 1200
frames for run-ahead and for each mapping after it.

The harness also loses most of its running time. The registry lane ran
several thousand frames to its scripted removal without
fast-forwarding, at the core's frame rate, which was over a minute.
Playback of a recording and netplay cannot fast-forward at all, so
those two checks now use 300 frames of the pattern, not 1200. make
check goes from about two minutes to about twenty seconds, with the
same checks.
2026-10-03 01:25:33 +02:00
libretroadmin 35080c9ffc bsv: a bucket that cannot grow fails the insert
uint32s_bucket_expand reports whether it took the index, and the
inserts give it the index before storing the object, so an object is
never kept where a lookup cannot find it: uint32s_index_insert returns
UINT32S_INDEX_NONE and insert_exact false, with the index unchanged.
The garbage-collection log prints its frame counter with
STRING_REP_UINT64 and its indices as unsigned.

The hash can be defined by the build; samples/tasks/bsv_statestream
runs once more with every object hashing alike, which puts the
buckets' growth under the oom lanes.
2026-10-03 01:18:57 +02:00
libretroadmin 82d728d59e bsv: the replay header's state size is written as 32 bits
bsv_movie_reset_recording writes the header's state size field from a
uint32_t, at REPLAY_HEADER_STATE_SIZE_INDEX. On a 64-bit big-endian
host the four bytes written from a size_t were its high half, so the
field read back as 0 and playback's starting position for seeking,
rewind and the timeline check fell inside the initial checkpoint.
2026-10-03 01:07:03 +02:00
libretroadmin 39ebd0399d s3: HMAC hex pairs are assigned, not initialised, for C89
The three hex_byte buffers in the HMAC and signature code take their
two digits by assignment; C89 only allows constant initialisers in an
aggregate. The compile matrix holds s3.c to the full C89 lane, since
no build compiles it in C89 otherwise.
2026-10-03 01:07:00 +02:00
LibretroAdmin 7704e26005 vfs: descriptor I/O for whole-file reads and writes on GameCube, Wii, PSP and PS2
GEKKO, PSP and PS2 join the descriptor lists, so a stream opened with
RETRO_VFS_FILE_ACCESS_HINT_SEQUENTIAL_BULK - filestream_read_file(),
filestream_write_file() and the atomic writer - goes straight to
newlib's open()/read()/write() instead of a FILE with a 64 KiB stdio
buffer.  A whole-file write is one write() and no fstat(), a whole-file
read one read(); on PSP each of those is a kernel call and on PS2 an
RPC to the IOP.

Exercised with the platform toolchains under PPSSPP (ms0:), Dolphin
(Wii SD card through the in-tree libfat) and Play! (host: through
fileXio): bulk opens land on the descriptor, files of 0 to 300000
bytes round-trip byte for byte, a shorter file written over a longer
one truncates, and the atomic writer creates and replaces.
2026-10-03 00:56:22 +02:00
libretroadmin ed7ec08a23 runloop: GET_LANGUAGE answers on builds without HAVE_LANGEXTRA
GET_LANGUAGE reports the stored user language on every build. Builds
without HAVE_LANGEXTRA have no language setting and hold English
there, which is what their menus show, so a core is told English and
never reads its own variable back unset. Raised in PR #19682 by
Anthony Schemel.
2026-10-03 00:53:55 +02:00
LibretroAdmin bc54ebac98 input: a joypad driver that starts over hands every controller its port back
The joypad driver is torn down and brought up again whenever the input
driver is - on a content load, a video reinit. Nobody has touched a
controller, but the driver reports them afresh, in whatever order and
slots it finds them, and a port reads a slot. Where that order is not
fixed - hotplug callbacks, a directory scan, pads that register on
their first event - players could come out of a content load on each
other's ports.

The device registry now knows the case. Before the driver starts it
notes which controllers are there and the port each is on. When every
one of them has been reported again, and nothing else has happened,
each is put back on its port by exchanging entries of
input_joypad_index.

Only that case:

- If a controller is missing, a new one appears, or one leaves again,
  it is not the same controllers coming back, and ports are left to
  be assigned as they always were. In particular a pad picked up
  alone is still player 1.
- It has to finish within ten seconds of the restart. A controller
  that turns up later was plugged in or woken by somebody, and moving
  players then would be a surprise, not a restoration.
- With a reserved device configured, ports are left to that.
- Controllers plugged in by hand, and sleep and wake, are untouched.

input_joypad_index is also what the config file saves, and a saved
exchange would outlive the session that needed it: the next start
enumerates afresh and the players come up exchanged. So what the user
configured is kept beside the setting while a restore is in force, and
the config file - and a core or content override - is given that.
Once anything else writes the setting, the menu included, what it
holds is the user's again and is what gets saved.

Tests:

- samples/input/device_registry: the same controllers back in other
  slots and another order are each on their old port and the mapping
  stays one-to-one; identical pads at known sockets do not swap; one
  missing, a new one, or one leaving again moves nothing; a disconnect
  for an empty slot, which drivers send as they shut down, does not
  stop it; two restarts in a row keep the ports from before the first.
- samples/runloop/frontend_overhead: two controllers are reported in
  each other's slots after a restart; each is back on its port, the
  saved config still has the configured index, and after a change to
  the mapping the saved value is the one on screen.

The ten-second limit is not covered by a test. Compiled through the
Android unity build.
2026-10-03 00:39:06 +02:00
libretroadmin b12ae7c1ee bsv: statestream checkpoints fail cleanly when an allocation fails
uint32s_index_insert reserves room in every per-object list and
allocates its copy before it changes the index, and reports
UINT32S_INDEX_NONE when it cannot; insert_exact reserves the same way.
uint32s_index_new returns NULL when its table, zero object or first
entry cannot be had, and both callers stop there. The statestream
writer checks its own buffers and the inserts, and fails the
checkpoint, which ends the recording since its indices may hold blocks
the file never got; bsv_movie_write_checkpoint also checks cur_save
and its encode buffer, and passes the failure on rather than writing
a size of -1. A recording whose first checkpoint cannot be written
does not start, and one rewound to its start ends if the checkpoint
cannot be rewritten.

samples/tasks/bsv_statestream gains oom and oomload lanes, which wrap
malloc, calloc and realloc at link time and fail each allocation of a
checkpoint write and of a load in turn.
2026-10-03 00:26:17 +02:00
LibretroAdmin 5c1f2c28e7 input: what poll invalidates is one word-sized store
input_driver_poll() cleared three per-port bool arrays with three
memsets, 48 bytes: the joypad mask cache's valid flags, and the two
flags of the frame's button view. They are invalidated together and
nowhere else as a group, so keep them together: a struct of three
16-bit words, a bit per port, cleared with one store.

The readers test and set a bit where they indexed an array. MAX_USERS
is 16 and is checked against the word at compile time.

No change in behaviour: the core-view lane's digests are the same.
Compiled through the Android unity build.
2026-10-03 00:26:04 +02:00
LibretroAdmin 6be12901ca samples/frontend_overhead: the core-view lane covers hosting netplay
Netplay was the one way input reaches a core that the lane did not
cover. Under netplay the host's own input goes through netplay's poll
and its state callback, and both of those read through the frame's
button view like any core.

The lane now hosts with nobody connected, plays the pattern, stops
hosting, plays it again, and compares the two: the core must see the
same sequence. It does, with no frame of delay. The host may hold
input back by its latency setting, so the sequences are matched at an
offset, as the recording is.

Nothing is announced, no relay is asked for and no port is forwarded;
the settings the lane changes are put back. Hosting needs a listening
socket, and a machine that will not give one is a skip, not a
failure. This is the local path only: a connected client, and what
arrives from one, are not exercised.
2026-10-03 00:13:12 +02:00
libretroadmin 6411f3c367 samples/bsv_statestream: decode statestream checkpoints from the tree
samples/tasks/bsv_statestream builds the shipping input/bsv/bsvmovie.c
with HAVE_STATESTREAM on, which no other lane does, with its block
index and the rmsgpack reader and writer. It round-trips recorded
checkpoints, then feeds the decoder sequences that name undefined
superblocks and blocks, a sequence longer than the superblock list, a
recording after a short sequence and a header with no block layout.
Each lane runs alone when named on the command line. The samples/tasks
workflow runs it plain and under ASan and UBSan.
2026-10-03 00:05:17 +02:00
LibretroAdmin 3bf55f90d3 input: a device registry that knows a controller when it comes back
A joypad driver knows a controller by the slot it sits in, and a slot
is only a position: pull a pad out and plug another in and the slot is
the same; plug the first back in elsewhere and its slot is different.
What should follow a controller around - its port, its mapping, its
calibration - has had nothing to follow.

input/input_registry.h gives every controller two things:

- A device id, for as long as the program runs. A controller that
  goes away and comes back gets the id it had. No two controllers
  present at once share one, and an id never passes to a different
  kind of controller.
- A handle, for one stay. It stops resolving when the controller
  leaves, when its driver reports it again, and when something else
  takes its slot. Something that kept a handle across any of those
  finds out; a kept slot number names whatever sits there now.

Which controller has come back is decided from what the driver
reports: its own name, the controller's name and USB ids, and where
it is plugged in if the driver knows. Among controllers that left and
match, the one last at the same place wins, then the one from the same
slot, then the one that left last; a pad moved to another socket is
still that pad. Two identical pads with no location cannot be told
apart by this.

This is the first piece of the registry in the input plan (WP-02), and
it is a mirror: it is fed from the two callbacks that apply a connect
and a disconnect on the main thread, it logs each arrival, return and
departure by device id, and nothing is assigned from it. Ports work as
before. It costs nothing on the frame path.

Tests:

- samples/input/device_registry builds the registry on a host:
  returns, handles ending, identical pads with and without a location,
  a driver starting over, a full table, and 200000 arrivals and
  departures with no two present controllers sharing a slot or an id
  and no handle outliving its stay. Run under ASan + UBSan.
- samples/runloop/frontend_overhead gains a lane: eight scripted pads
  are eight devices in their slots, and one pulled and plugged back
  in is dropped, then returns with the same id and a new handle.

Compiled through the Android unity build.
2026-10-03 00:03:38 +02:00
libretroadmin c6f3adb51a bsv: refuse statestream checkpoints that point outside the replay
A statestream checkpoint names superblocks and blocks by index and
lists the superblocks of the state in order, all from the replay file.
The decoder refuses an index the replay never defined instead of
copying from it, and a replay whose header gives no block layout. The
superblock list now carries its length, superblock_seq_len, and grows
when a sequence or a recorded state needs more entries than it holds;
growing it drops the unchanged-superblock shortcut until the next save,
as the writer already did. The decoder's allocations are checked, and
an item it bails out on is freed.

uint32s_index_get reads the last frame addition from additions[len - 1]
when it logs where a collected index came from.
2026-10-03 00:00:56 +02:00
libretroadmin d7c14a0ac8 bsv: hold replay checkpoints to the sizes in their header
A checkpoint's state, encoded and compressed sizes come from the replay
file. Uncompressed data has to be as long as its encoding, and a raw
state as long as the state, before anything is read: cur_save holds the
state size, and uncompressed raw data is read into it directly. A
checkpoint whose sizes disagree ends the movie. cur_save_size changes
only once cur_save holds a decoded checkpoint, so a skipped or failed
one leaves it describing the buffer, and the cleanup never frees
cur_save, which the movie keeps.

samples/tasks/bsv_checkpoint runs the function against mismatched,
truncated and skipped checkpoints under ASan and UBSan, in the
samples/tasks workflow.
2026-10-03 00:00:52 +02:00
LibretroAdmin ba7dfcd9a5 samples/frontend_overhead: the core-view lane covers the other turbo modes, run-ahead and a recording
The lane's turbo digest is wrong on master, and the lane fails there.
It was recorded on a tree without d5bf86d93, which landed first: with
the empty Turbo Bind, the turbo path read input_remap_ids[port][-1]
and cleared whichever button that word happened to name while the
modifier was held. The recorded sequence had that in it. d5bf86d93
fixed the read, the sequence changed, and the digest is updated to
what cores see now.

Three gaps in what the lane pinned are closed. Each new digest was
taken from the state path as it was before the button view
(3a6a1e9c4 reverted locally), and the view reproduces all of them:

- The other three turbo modes - toggle, single button, single button
  hold. They keep state across buttons, which is where reading button
  by button and reading the mask were most likely to part. They do
  not: the two readings agree frame for frame in all three.
- Run-ahead by one frame. The core is run twice an iterate, the
  second time on replayed input, and must see what it sees without
  run-ahead: the digest is the default mapping's. The lane checks
  that the core really ran twice a frame; the harness replaces the
  program's main(), which is what makes run-ahead available, so the
  lane does that itself.
- Input recording. The pattern is recorded, then played back with
  the pad idle, and the 1200 frames the core reads on playback must
  be the 1200 it read while recording. Recording and playback start
  through tasks, so the sequences are matched at whatever offset the
  start left.

Netplay is still not covered.
2026-10-02 23:52:27 +02:00
Anthony Schemel 02028ea990 Check allocations that were dereferenced unchecked
- menu_driver: driver_ctx->init() returns NULL when an allocation fails
  (xmb, ozone, rgui and materialui all can), but the result was
  written through before the !menu_st->driver_data check below.
- task_screenshot: task_init() returns NULL on OOM; the threaded
  screenshot path wrote through it. Free the state and fail instead.
- bsvmovie: bsv_movie_load_checkpoint read into handle->cur_save and
  into compressed_data without checking either malloc, and decompressed
  into an unchecked calloc on the zlib and zstd paths.
- uint32s_index: uint32s_bucket_expand memcpy'd into an unchecked
  calloc, and assigned realloc's result over the only pointer to the
  old block. On failure leave the bucket as it was.
2026-10-02 23:53:20 +02:00
Anthony Schemel 5c34f32f08 s3: encode '&', '=' and '?' in the object key
s3_url_encode is called only from s3_build_request_url, for the object
key, yet it left '&', '=' and '?' unencoded as query delimiters. A '?'
in a key then ends the path where s3_build_canonical_uri_from_url looks
for the query (strpbrk(path_start, "?#")), so the request goes to the
wrong key and is signed over the wrong URI. '&' and '=' are common in
save names ("Rocky & Bullwinkle") and S3 percent-encodes them in its
own canonical URI, so the SigV4 signature most likely mismatches.
Encode all three; '/' stays literal.
2026-10-02 23:45:36 +02:00
LibretroAdmin 3a6a1e9c4f input: a port's RetroPad buttons are compiled once a frame
A core that reads its buttons one by one went through
input_state_internal() for each: the port mapping, the remap table,
turbo, hold, the overlay and analog-to-d-pad, sixteen times a frame
for one pad. A core that asks for the bitmask got all sixteen from one
such walk.

Keep the result of that walk as the frame's view of the port: one
word holding the sixteen buttons as the core is to see them. It is
compiled through the existing mask path - nothing about how a button
is decided changes - when a core asks for the port's mask, or for a
second button in the frame. After that every button and mask query
for the port reads the word. input_driver_poll() invalidates it, and
so does a core's first analog request, which switches analog-to-d-pad
off for the port.

The first button of a frame still takes the old path, so a core that
reads a single button a frame pays what it did.

In samples/runloop/frontend_overhead, on the release build:

- sixteen buttons read one by one: about 65 ns a query before, about
  25 ns now with the compile spread over them;
- 256 queries a frame: 8 ns each;
- one query a frame: unchanged, 2.7 us for the whole frame against
  2.9 us before, which is within the noise of this machine.

The lane added in the previous commit holds this to what cores saw
before: reading button by button and reading the mask give the same
buttons frame for frame, and the same recorded digests, under the
default mapping, a remap, analog-to-d-pad, turbo and hold. The
digests did not change.

The port a core passes is now checked before it indexes the per-port
arrays here; one past MAX_USERS read and wrote outside them.

Not covered by a test: run-ahead, netplay and input recording. All
three reach this function the same way a core does - recording still
logs every query - and each run of the core polls, which invalidates
the view. Turbo modes other than the classic one are not in the
lane either.
2026-10-02 23:43:03 +02:00
LibretroAdmin 932aa6a984 samples/frontend_overhead: what a core sees, recorded
The input redesign replaces the state path - the code between a
core's input_state call and the driver - with a view compiled once a
frame. Before that path changes, this pins what it does.

A synthetic pad plays a fixed pattern over the sixteen buttons and
both sticks for 1200 frames, with the turbo and hold modifiers held
for stretches. The harness core reads pad 1 every frame, and the lane
records what it saw: once reading button by button, the way older
cores do, and once reading the bitmask. Five mappings: the default,
A and B swapped by a remap, the left stick as a d-pad, turbo, and
hold.

Two checks for each:

- The two readings are the same, frame for frame. They go through
  different code in input_state_internal(), and a view that serves
  single buttons from the port's mask relies on their agreeing. On
  master they do, in all five.
- The sequence matches a recorded digest. A change in what cores see
  fails here, and has to be made on purpose.

The pad is the test joypad driver with its button, axis and state
functions replaced by ones the lane drives frame by frame, so the
pattern is not limited by the driver's script. Turbo and hold state is
reset before each reading, and the core's first analog read - which
switches analog-to-d-pad off for the port - is made before recording,
so both readings start from the same place.
2026-10-02 23:43:00 +02:00
libretroadmin d5bf86d935 input: keep Turbo Bind and Turbo Button inside their tables
Turbo Bind and Turbo Button hold a RetroPad ID that indexes
input_config_bind_order in the menu and input_remap_ids in the turbo
paths. Config and remap loading clamp both to their menu ranges, Left
in the menu gives the empty bind only on a setting whose range has
one, so Turbo Button stops at its first entry or wraps, and both menu
handlers find the entry by its position in the bind order. The turbo
clear skips the empty bind, which has no RetroPad button.

samples/tasks/turbo_bind walks both settings in both directions with
wrap-around on and off, feeds them out-of-range values and checks the
load clamp and the empty-bind guard under ASan and UBSan; it runs in
the samples/tasks workflow.
2026-10-02 23:45:31 +02:00
Anthony Schemel 8c4d49685c menu_setting: keep retropad-bind left/right inside input_config_bind_order
Right on the last entry of a retropad-bind setting read
input_config_bind_order[i + 1] past the end: Turbo Bind's range runs to
RARCH_ANALOG_BIND_LIST_END - 1, the last index of the 24-entry array.
Without wraparound the out-of-range value was then stored in
input_turbo_bind. Stop at the last entry; the wraparound block already
handles the wrap.

Left indexed input_config_bind_order[value] with the stored value
unchecked. input_turbo_bind is read from the config file without a
clamp, so a hand-edited value of 24 or more read past the end. Treat a
value above the setting's max like a negative one.
2026-10-02 23:45:31 +02:00
LibretroAdmin 9c7bdc3855 input: a core's rumble and LED calls are stored, and written after the frame
A core calls set_rumble_state and set_led_state from inside
retro_run(), and both went straight to the driver: an ioctl or a
transfer for a motor; for an LED a sysfs open, write and close, an X11
round trip, or an injected key press. As many times a frame as the
core called, on the core's own stack. A core that reports its rumble
level every frame paid for a device write every frame, and one that
sets it several times a frame paid for each.

The calls now store what was asked for (input/input_output_store.h),
and the frame loop writes it once the core has run: one driver call
for each motor or LED that was set, with the frame's last value.

- An output set to the value it already had is still written once
  that frame. Drivers whose effects run out rely on a core repeating
  itself, so the store remembers that a slot was set, not only what
  to.
- The write happens after the cheats, which can rumble too, and so
  after run-ahead and netplay replays: several runs of the core in
  one iterate are one write.
- What a core sets while loading or unloading is written when that
  call returns. No frame follows an unload to write an LED it turned
  off.
- CMD_EVENT_RUMBLE_STOP writes its zeros at once, as before, and
  drops whatever was waiting so it cannot restart a motor.
- Posting is safe from any thread, since some cores call from one of
  their own; the driver is now only ever called from the main thread.

What a core can notice:

- A motor or LED changes up to a frame later than it did: at the end
  of the frame in which the core asked, not in the middle of it.
- set_rumble_state returns what the driver answered the last time
  that motor was written, and before the first write whether a driver
  with motors is loaded, where it used to return the answer to this
  very call. The port and effect are now checked: a port past
  MAX_USERS indexed the joypad-index setting out of bounds.

This is the first part of the output scheduler in the input plan
(WP-09). The write is still on the main thread; moving it to the
device's own thread comes with per-device sessions.

Tests:

- samples/input/output_store builds the store on a host: a post
  writes nothing, many posts in a frame are one write with the last
  value, a repeat is still written, a drop forgets, and with three
  threads posting against a taker every output ends on its last
  value. Run under ASan + UBSan and under TSan.
- samples/runloop/frontend_overhead gains a lane in which the
  harness core makes six rumble calls a frame and the joypad driver's
  set_rumble is counted. Before: 1800 calls, 1800 driver writes inside
  retro_run(). After: none inside, one per motor after it, each with
  the last strength; a stop writes zeros and leaves nothing behind.

The frame costs 2.9 us in the harness before and after. Compiled
through the Android unity build; the menu harnesses pass.
2026-10-02 23:20:14 +02:00
LibretroAdmin bc29ca1fd0 runloop: late polling takes effect when the poll mode changes at runtime
The core's input_state callback was chosen once, when the core loaded:
the late-polling one, whose first query of the frame polls, or a plain
one. The poll mode can change afterwards - Poll Type Behavior in the
menu calls core_set_poll_type(), and a core can set an override - and
nothing chose again.

So a core loaded with normal or early polling and switched to late
kept the plain callback. input_poll does nothing under late polling,
the early poll no longer ran, and the only poll left was the fallback
after retro_run(). The core then read input sampled before the
previous frame's pacing wait: up to a frame older than late polling is
there to give, and older than either mode it was switched from. It
lasted until the content was reloaded.

Give the core one callback and read the mode at the call. The frame's
first query polls if late polling is in force and the frame has not
polled yet. RETRO_CORE_FLAG_INPUT_POLLED now means that in every mode:
set by the early poll before retro_run(), by input_poll under normal
polling, or by the first query under late; cleared at the start of
each frame that does not poll early. Past the frame's poll a query
costs one test of that flag, as it did under late polling; the flag is
no longer written on every query.

Netplay and run-ahead are unaffected: both wrap the same callback, and
netplay forces normal polling as before.

samples/runloop/frontend_overhead gains a lane that counts where the
frontend polls. The joypad driver's poll is counted between four
points of the harness core's retro_run() and the two ends of the
iterate, and each mode must poll once a frame in its own place and
nowhere else: early before retro_run(), normal in input_poll, late in
the first input_state. The check target now runs the harness three
times, loading the core with late, normal and early polling
(HARNESS_LOAD_POLL), since which mode the core is loaded with is what
decided the callback.

Before this change the lane fails for a core loaded with normal or
with early polling and switched to late: 300 polls in 300 frames, all
after retro_run(). The opposite switch, late to normal or early, was
already correct.

Cost, from the harness on the release build: input_state stays at 60
to 70 ns a query in every mode and the frame at 2.9 us, before and
after.
2026-10-02 22:48:39 +02:00
libretroadmin 20df49def8 archive_file: stream deflated members to disk as they inflate
A member extracted to a file is written through a temporary file
beside its target, opened when the member starts and renamed over the
target when it completes, as filestream_write_file_atomic() did with
the whole member at the end. The ZIP backend inflates a deflated member
larger than ZIP_STREAM_WINDOW (256 KiB) through a window of that size
and writes each full window out, with zlib and with the built-in
inflater alike, instead of holding the whole member in memory and
writing it in one call; other members are still decoded whole and
written to the same temporary file. A CRC failure, a cancel or any
other member that does not complete removes the temporary file and
leaves the target as it was. file_stream gains filestream_open_atomic()
and filestream_commit_atomic(), the two halves of
filestream_write_file_atomic(), which is now built on them.

decompress_window_test gains stream and keep lanes: a 1 MiB deflated
member reaches the disk in writes no larger than the window, and a CRC
failure or a cancel mid-member keeps the previous file with no
temporary file left. Linux-samples-tasks.yml also runs the test against
the built-in inflater (BUILTIN_DEFLATE=1).
2026-10-02 22:47:02 +02:00
LibretroAdmin ad82be3467 Wayland backport: C89 loop counters in the marshal fallbacks 2026-10-02 22:46:35 +02:00
LibretroAdmin 000f21ec53 Wayland: wait for the GL frame callback on its own queue
The frame callback the GL context waits on when it throttles to the
compositor (max swapchain images of 2 or less) goes on an event queue
of its own, which the swap path reads and dispatches alone. Under
threaded video the main thread's input dispatch no longer runs done
against the wait's state, the video thread no longer runs input
handlers, and a timed-out wait cannot destroy a callback the other
thread already did.

The wait moves into gfx/common/wayland_present.c as wl_frame_t, and
samples/gfx/wayland_present covers it. Where the backport finds no
per-queue reads in libwayland, proxies stay on the default queue as
before.
2026-10-02 22:38:43 +02:00
libretroadmin c35bdedc0f samples/frontend_overhead: the input lanes under early, normal and late polling
The input plan requires that no poll mode add a frame between a device
and the core. The test joypad's script now presses and releases pad 1's
B once under each of early, normal and late polling, switched as the
menu switches them, and the harness core records the run in which it
first sees each edge: all three modes must see both edges in the same
frame relative to the script. Each mode also prints where its poll cost
lands as the core sees it - in input_poll for normal, in the first
input_state for late, before retro_run (out of the core's sight) for
early.

The frame clock here is logical, so this catches an extra frame, not
input sampled early within one: a core loaded under normal polling and
switched to late keeps the plain state callback, and its poll is the
fallback after retro_run, before the frame-pacing wait. The input age
timestamps (WP-14) are what will measure that.
2026-10-02 22:31:43 +02:00
LibretroAdmin 9c6692e25d webOS: keep the window through the common Wayland keep
webOS keeps its window across a content load the way every Wayland
context now does: the whole context is kept by gfx_ctx_wl_free_common
and taken back by the next init, with the platform's own teardown only
when it is released. Its field-by-field stash, the listener user-data
repointing it needed, and the separate release entry point go.

A load that ends with no core at all releases the kept window on any
Wayland platform.
2026-10-02 22:20:03 +02:00
LibretroAdmin ec3877ad88 gfx_widgets: keep the persist-across-reinit bit out of the shared flags
Whether widgets persist across a driver reinit is main-thread state,
now its own bool instead of a bit in dispgfx_widget_t.flags, where the
video thread sets and clears DISPGFX_WIDGET_FLAG_MOVING while closing
content flips it.

gfx_widgets_msg_queue_race_test gains a TSan lane ending animations on
one thread while another flips it.
2026-10-02 22:20:00 +02:00
LibretroAdmin 404d976011 Wayland: dispatch presentation feedback on the presenting thread
Feedback objects live on their own event queue, dispatched only from
the swap path, so the main thread dispatching input under threaded
video never runs the presented/discarded handlers against the feedback
list and timing the video thread uses.

Presentation-time handling moves into gfx/common/wayland_present.c
with its state in wl_present_t, so samples/gfx/wayland_present can
drive the real code against an in-process compositor with input
dispatched on another thread, under TSan and ASan.
2026-10-02 22:19:58 +02:00
libretroadmin 967c05df29 android: show the display's EDID under Display Information
Display Information > EDID is built on Android and reads the block the
kernel driver keeps in sysfs: the DRM connector node through the reader
the Linux servers share, then the Amlogic vendor HDMI driver's rawedid,
which most Android TV boxes ship and which prints the block as hex
text. Android has no API for the raw block, so devices whose kernel or
SELinux policy keeps both nodes out of reach show the existing
explanation for a display with no EDID rather than a block assembled
from guessed timings.

The Android build turns on the modeline engine to carry the EDID
reader. Its display server advertises no modeline capability, so the
CRT SwitchRes menu stays hidden there.
2026-10-02 22:19:03 +02:00
libretroadmin 2ee0fc64b2 samples/frontend_overhead: input lanes, the baseline for the input redesign
The input plan's first gate is measurement: what input_poll and
input_state cost a core today, before any of the redesign lands. The
harness core times its own input_poll, the frame's first input_state -
where the poll happens under the default late polling - and every
further query, and the harness runs it with no pad, one pad read 16 and
256 times a frame, and eight pads, from the test joypad driver playing a
scripted set of connects with an autoconfig profile, as the synthetic
provider. Each window prints the numbers and fails on a heap call inside
input_poll or input_state, on a cost only a blocking call would reach,
or on a per-query cost that grows with the query count.

The pads connect after the frame-cost lane, so the lanes before keep
measuring with none. The input lane steps frames as the main loop does,
iterate then task_queue_check, since a pad's autoconfig is a task; no
window spans a connect, which runs in input_poll and allocates there.
2026-10-02 22:18:33 +02:00
LibretroAdmin f2d6adc985 winraw: number axes in DirectInput's DIJOYSTATE2 slots
The winraw joypad driver places each axis by HID usage - X, Y, Z, Rx,
Ry, Rz on 0-5, Slider and Dial on 6-7 in value-cap order - the slots
the dinput driver uses, so a device's axis numbers no longer depend on
the order its HID value caps come back in.

samples/input/winraw_joypad_axes runs the driver's enumeration and
report parsing under Wine against fake HID caps, in descriptor and
reversed order.
2026-10-02 21:41:30 +02:00
LibretroAdmin 7b1fce4bf5 wayland: keep the window when switching between GL and Vulkan
The kept window is now taken back by whichever Wayland context comes
next, not only by the one that kept it.  Each context driver tears its
own surface state down before keeping - the EGL window, or the Vulkan
surface and swapchain with whatever the Vulkan WSI attached - so the
next one finds a bare surface, and a video driver switch no longer
closes the window.
2026-10-02 21:20:13 +02:00
LibretroAdmin a0e2372730 wayland: fullscreen toggles on a kept window
A fullscreen toggle reinits video, and the window kept across it is
in the state the toggle is leaving, which a new window never was:

- Leaving fullscreen now unsets it and waits for the windowed
  configure, as entering it waits for the fullscreen one.
- An adopted window no longer ignores configures; that guards the
  splash of a cold init, and there is none.
- An ignored libdecor configure is still committed, which is what
  acknowledges it; dropping it left libdecor's frame state stale
  and the compositor waiting, so the next fullscreen request did
  nothing.
- A windowed size request becomes the floating size, which a
  configure leaving the size to the client restores.
2026-10-02 21:20:10 +02:00
LibretroAdmin 9fc4fe297d qb: run the check compiles ahead of the checks
Before config.libs.sh runs, a dry run of it in a subshell records the
compile each check would run, and background workers, one per processor
beyond the first, run those compiles while the checks go through in
order; a check waiting on a worker runs other pending compiles
meanwhile. A check takes a result only when its command and test
program are exactly the recorded ones and compiles in place otherwise,
so the dry run's guesses decide how much runs ahead, never what
configure finds. QB_JOBS sets the processor count; 1 runs every check
in place, as does a single-processor machine.
2026-10-02 21:19:56 +02:00
LibretroAdmin 0b07805d66 autoconf: Save Controller Profile saves the port's binds for its device
config_save_autoconf_profile() takes the port whose binds are saved and
looks up the device assigned to it: the binds and their labels come
from the port, the joypad driver, name, vid/pid and autoconf binds from
the device. The menu passes the port. A port driven by a device of
another index now saves its own binds instead of being rejected as an
invalid profile.

The save creates the autoconfig directory when it does not exist yet,
as the remap save does for its own, and clears the bind labels it
frees, so saving a second time no longer frees them again.

The test joypad driver gains a step that saves the profile for a port,
and the autoconf suite covers a labelled profile saved twice, a port
mapped to another device index and a missing autoconfig directory.
2026-10-02 20:56:50 +02:00
libretroadmin 8c6700405d Core updater: cache installed core CRCs against size and mtime
Update Installed Cores keeps each installed core's CRC in
core_backups/core_updater.crc, beside the core backups, keyed on the
size and modification time the file had when it was hashed, and a core
whose file still has both is not read again: a repeat run where nothing
changed hashes no core at all. The installed-only list parse walks the
libretro directory once with retro_dirent_stat() instead of a stat per
listing line, and records each installed core's size and mtime on its
list entry. A core without usable metadata (SMB and SAF report no
mtime), a missing or corrupt cache, or a changed size or mtime hashes
the core as before, and only a run that reaches its end writes the
cache, keeping the cores it reached.

update_installed_scan_test gains a CRC cache lane counting bytes hashed
and core downloads at the link boundary: a first run hashes every core,
a repeat none, a touched core is hashed again, a changed core is hashed
and downloaded, and a corrupt cache falls back to hashing every core.
2026-10-02 20:55:27 +02:00
libretroadmin de4f557e13 Core updater: a core download whose transfer cannot start fails
When task_push_http_download_file() refuses the transfer, the download
task marks the transfer as failed and goes to its error state, instead
of moving on to wait for an extraction that only the transfer's
callback would start. Update Installed Cores then moves on to the next
core rather than waiting on that download for good.

update_installed_scan_test gains a lane that changes an installed core,
refuses its download at the link boundary, and asserts the run
completes.
2026-10-02 20:55:21 +02:00
LibretroAdmin a37a9a29b5 archive_file_zlib: verify each ZIP member against its recorded CRC
A ZIP member is accepted only when its decoded size and CRC-32 match
the central directory, so a damaged archive fails instead of
installing or loading altered bytes.  For DEFLATE members the CRC runs
over each slice's output as it is produced, while it is still in
cache; STORED and Zstandard members are checked whole.  7z members
were already verified by the 7z reader.

The extraction task reports a member that fails after it was started
- a decode, CRC or write failure while the member is parked across
calls - as a failed extraction rather than completing as if nothing
went wrong.

archive_zip_test runs its member sequence with every recorded CRC
wrong and checks that no member is written; decompress_window_test
checks that a bad CRC fails the task, for a member that fails as it
starts and for one that fails after parking.
2026-10-02 20:46:05 +02:00
LibretroAdmin 0911101bd4 qb: run the moc check alongside the remaining checks
The moc check needs only Qt's flags and the C++ compiler, so it starts
in the background as soon as the Qt checks settle and qb.moc.sh collects
the answer; its log is added to config.log where the check is reported,
and it runs again in place if the Qt flags changed after it started.

Header and predefined-macro checks compile without linking, since they
pass no libraries.
2026-10-02 20:30:05 +02:00
LibretroAdmin 38014aee43 vfs: remove a leftover aside file only when it is in the way
The replace-on-rename branches - PSP and Vita, and the targets whose
filesystem refuses to rename onto an existing name - move the
destination aside first, and remove an aside left by an interrupted
run only when that move fails.  On libfat and FatFs a lookup of a
missing name scans the whole directory, so replacing a file costs one
directory scan less; an aside actually left behind costs one refused
rename.

vfs_rename_replace_test checks that an overwrite removes no name that
does not exist.
2026-10-02 20:07:39 +02:00
LibretroAdmin c24fd90825 file_stream: a failed atomic write leaves the old file in place
filestream_write_file_atomic() removes the target and retries the
rename only under a frontend VFS, whose rename may refuse an existing
destination as the libretro VFS API allows.  The built-in rename
replaces the destination on every platform and, where the filesystem
cannot rename onto an existing name, moves it aside and restores it
when the replacement cannot be moved in - so a failed rename leaves
the old file as it was, as the function documents.

libretro-common/samples/file/vfs/vfs_rename_replace_test builds the
VFS with -DGEKKO and wraps rename/remove/stat to refuse a rename onto
an existing name, as libfat, FatFs and the 3DS/Switch/Wii U/PS3
filesystems do, and checks a fresh write, an overwrite, a leftover
aside file, a replacement that cannot be renamed in, and a frontend
VFS whose rename refuses an existing destination.
2026-10-02 20:07:35 +02:00
LibretroAdmin 6a8e2d959d qb: run configure without a subshell per variable lookup
Indirect variable reads use eval assignments and word splitting uses
plain unquoted expansions, so the helpers no longer fork a subshell for
every option they touch; add_opt looks options up with a case match.
Under zsh, configure switches to sh emulation so unquoted expansions
split as they do in a POSIX sh.

The check helpers rewrite their test files each time and an EXIT trap
removes them once, instead of an rm process after every check.
check_header now starts from an empty file for C++ checks too.
2026-10-02 20:06:56 +02:00
LibretroAdmin 7f75193cd6 sdl3: a failing capture read is not logged
The microphone read is asked every frame, and a stream that fails
fails every frame; each failure was a logged error. The read returns
-1 as before, and the frontend counts the reads that come back empty.

sdl3_mic_wait reads a failing stream for a second of frames and
counts the lines.
2026-10-02 20:06:34 +02:00
LibretroAdmin 2422e32d8a oss: a device that cannot answer reports no room and no size
write_avail() is asked every frame, and a device that has gone away
failed SNDCTL_DSP_GETOSPACE every frame, each failure a logged error;
it now reports no room without a line. buffer_size() answers 0 when
the device cannot say, which turns rate control off for the session
as audio_driver.h has it, where the 1 it answered gave the rate
control a setpoint of half a byte.

samples/audio/oss_report includes the driver over Linux's soundcard.h
and asks both of a closed descriptor for a second of frames.
2026-10-02 20:06:30 +02:00
LibretroAdmin 0c4ac6d54f wayland: declarations first in the GL context's set_video_mode and swap 2026-10-02 20:04:53 +02:00
LibretroAdmin 0b978fb8cd wayland: free each output's make and model strings 2026-10-02 20:04:50 +02:00
LibretroAdmin 8a01cdbce8 wayland: release the data device with the window 2026-10-02 20:04:47 +02:00
LibretroAdmin a08aca5ca7 wayland: replace rather than leak an EGL window made before set_video_mode 2026-10-02 20:04:43 +02:00
LibretroAdmin 95c99109e3 wayland: keep the window across video reinits
A video reinit - a content load or close, a driver reinit, a
fullscreen toggle - used to close the window and map a new one.  The
context is now kept whole instead, display connection, surface,
toplevel and all, and the next init of the same context driver takes
it back: no new window, no new configure round, and every proxy still
hands its listener a live context.

Only what a context driver puts on the surface itself is dropped when
keeping - tearing control and the colour-management tagging - so the
next one can set its own.  A window no driver takes back, as when the
new video driver is not Wayland's, goes as soon as that driver is up;
shutdown destroys it as before.  webOS keeps using its own stash.
2026-10-02 20:04:40 +02:00
LibretroAdmin 6545a47e75 CI: install ffmpeg for the TSan sample suites
rh264_lossless_test and rh265_lossless_test build their corpora and
reference decodes with ffmpeg (libx264, libx265), which the TSan lane's
package list did not carry, so both failed before running. Ubuntu's
ffmpeg package ships both encoders.
2026-10-02 20:03:35 +02:00
LibretroAdmin bfb6bb0a58 CI: the threaded sample suites under ThreadSanitizer
Sixty-one threaded suites - sixty-seven test binaries - ran in CI only
under ASan, which cannot see a race. tools/tsan-samples.sh builds each
suite in tools/tsan-samples.list with SANITIZER=thread and runs its
tests with halt_on_error, using a suite's tsan.supp where it ships one;
the list says which suites are left out and why.
Linux-tsan-samples.yml runs it.
2026-10-02 13:57:19 +02:00
LibretroAdmin a6c684595f wayland_resize_test: no compositor socket is a failure, not a crash
Without XDG_RUNTIME_DIR the in-process compositor gets no socket, and
the client went on to use a display it never connected. It now reports
that and stops.
2026-10-02 13:57:12 +02:00
LibretroAdmin 3a5b454a30 archive_file_zlib: keep the inflate stream and read buffer per transfer
A ZIP transfer creates its inflate stream once and resets it for each
DEFLATE member (inflateReset / rinflate_reset), and keeps its 128 KiB
read buffer from member to member, allocated only when the archive is
not mapped.  A Zstandard member's whole-member read buffer is released
once that member is decoded.

archive_zip_test gains a case extracting DEFLATE and STORED members,
including one spanning several input slices, through one transfer and
checking every member byte for byte.
2026-10-02 13:56:59 +02:00
LibretroAdmin fb66179c8f tasks/decompress: extract under the shared per-frame I/O window
Each handler call steps the archive for as long as the shared
task_nbio_slice window allows, at least one member, so with Threaded
Tasks off - the default on GEKKO, PSP and PS2 and on builds without
threads - a frame extracts as many members as fit in the window.

samples/tasks/decompress/decompress_window_test compiles the shipping
task against the real unthreaded queue, ZIP backend and window with a
virtual clock: a whole archive fits one unbounded window, an exhausted
window still extracts one item per call, the subdir variant is
windowed the same way, a DEFLATE member resumes across slices, and
cancelling part way releases everything.
2026-10-02 13:56:55 +02:00
LibretroAdmin 7142042a2a pipewire: a process cycle with no buffer is counted, not logged
Both process callbacks run on the graph's thread, and a cycle that
finds no buffer to take logged a line there. It is counted with the
capture overruns instead, and each side logs its total once, when the
microphone closes or the driver is freed.

The pipewire_write suite runs both callbacks with no buffer to take.
2026-10-02 13:51:36 +02:00
LibretroAdmin 806d60a603 wasapi: the shared-mode capture FIFO holds a whole packet
A captured packet goes into the FIFO whole or waits for room, and the
FIFO's length came from the shared-buffer setting alone; set shorter
than one engine packet, no packet ever went in and the microphone
delivered nothing for the rest of the session. The FIFO is now the
setting but never less than the engine buffer.

samples/audio/wasapi_mic runs the driver's text against the shared fake
WASAPI with a capture client added: a FIFO set shorter than a packet
takes every packet, and a packet with no room waits until the FIFO is
read.
2026-10-02 13:51:32 +02:00
LibretroAdmin 9240e6f5b2 alsa: a capture read brings an overrun or suspended stream back
A capture the caller does not read in time overruns and stops, and a
suspend stops it too; snd_pcm_start() refuses such a stream until it
is prepared again. The read recovers it first, and starts it only if
it is then prepared - a resume can bring it straight back to running.
A stream the frontend paused, or one that is gone, is left alone. The
read logs nothing; the frontend counts the reads that fail.

The alsa_lifecycle suite scripts the stream state and snd_pcm_start()
and covers an overrun, the read after it, a suspend with and without
a resume, and a paused capture.
2026-10-02 13:51:25 +02:00
LibretroAdmin 2e5951fac4 config_file: record '#reference' paths at full path size
config_file_add_reference() stages the abbreviated-or-relative path in
a PATH_MAX_LENGTH heap buffer, freed once the reference list has taken
its copy, so a reference longer than NAME_MAX_LENGTH (255 bytes on
desktop, 127 on the small-memory platforms) is recorded whole. A
pathless config hands the path to the list directly.

config_file_test covers a 306-byte reference.
2026-10-02 13:35:26 +02:00
LibretroAdmin c9003029f0 file_path: resolve abbreviated-or-relative paths at full path size
fill_pathname_abbreviated_or_relative() joins a relative path onto the
referencing file's directory and normalises it in a PATH_MAX_LENGTH
buffer of its own, so a join whose '..' segments push it past the
caller's output size still resolves; only the chosen result is written
to the output. The relative form's depth is counted from the same
prefix split path_relative_to() uses. Fixes '#reference' presets that
reach across several directory levels (#19687).

fill_pathname_test covers a reference whose raw join exceeds a 256-byte
output, plus the shallow and absolute cases.
2026-10-02 13:31:53 +02:00
LibretroAdmin 84e0731fc8 android: one place writes key presses and releases, with a test
For nine days a press of Back left the keyboard's X key held for good.
2131e2b9f made the Back-as-X alias set X on the keyboard row as well as
on the event's own row, and the release only cleared the latter; it was
fixed in 4e327bafd. X is bound to RetroPad A by default, and the run
loop drops all controller input after a menu toggle until every button
is up. So after one press of Back - which on a handheld is how the menu
gets opened - the menu stopped answering the controller, and so did the
game on the way back, while touch kept working. That is issue #19436.

The press and the release were two halves of a switch statement that
had to be kept in step by hand, and a rewrite of DPAD_CENTER into ENTER
in the same function had already broken a remote's OK button once
(issue #18819). Move the writes into android_key_state.h, free of NDK
types, the way android_kbd_route.h holds the key routing: one function
takes a key event and sets or clears everything that goes with it. The
driver calls it; the keycodes and sources the header spells out are
checked against the NDK's at compile time. Behaviour is unchanged.

One case is new. A pad removed while it holds Back had its own row
cleared and the X it put on the keyboard row left behind - the same
stuck key by another route. Its row is now released through the
header, which takes that X with it.

samples/input/android_key_state builds the header on a host and pins:

- whatever a press sets, the release clears: every keycode, from every
  source, on every row;
- the same after 200000 interleaved presses and releases, once
  everything held has been released;
- a keycode outside the rows is dropped, and nothing is written past
  them;
- Center is Enter on the keyboard row and stays keycode 23 on a pad
  row;
- Back presses X on its own row and the keyboard row while held, and
  not at all from a gamepad;
- a pad removed while holding Back leaves nothing behind.

The missing clear is put back by a sabotage mode and asserted to be
caught.

Compiled with the NDK through the Android unity build.
2026-10-02 13:30:28 +02:00
LibretroAdmin 0c40c039fa pipewire: a write takes what fits, in whole frames
A write longer than the ring's high-water mark took nothing and logged
an error, on every call: at a low latency setting one frame of audio
is longer than the mark, so the stream went silent. A write now takes
as much as the ring has room for, in whole frames; blocking, it waits
for the rest in parts as the graph frees room. The capture callback
counts its overruns and underruns on the graph's thread instead of
logging each one, and the total is logged when the microphone closes.
Unused locals and the disabled tracing blocks go.

samples/audio/pipewire_write includes the driver, built against the
system's PipeWire headers, with the graph standing in as a loop wait
that plays a quantum out of the ring; the samples/audio job installs
libpipewire-0.3-dev for it.
2026-10-02 13:22:44 +02:00
LibretroAdmin ca178b7ec6 audioworklet: write returns bytes, and frames given up are counted
write returns the bytes it took into the ring, as every driver does;
the threaded pipeline advances its pending audio by that count and the
inline path writes the rest from there, so a count in frames had seven
eighths of each write sent again. Frames a write gives up on are
counted and logged as one total when the driver is freed, rather than
a line from the write.

samples/audio/audioworklet includes the driver, built against the
tree's emscripten stand-ins, and drives its write on a ring of known
size.
2026-10-02 13:22:41 +02:00
LibretroAdmin 325ad55577 ctr: a write takes at most half the ring
Both 3DS drivers keep a 2048-frame ring and wait for room up to half
of it; a write now takes at most that, in whole frames, and reports
what it took. NDSP's copied the caller's whole length into its ring
with a single wrap, so a write longer than the ring ran past it; CSND's
overwrote its own audio before it played.

The 3DS lane is samples/audio/ctr_audio, covering both drivers: its
<3ds.h> stand-in gains NDSP, and long writes onto both, across the
ring's end and blocking, are checked under ASan.
2026-10-02 13:22:37 +02:00
LibretroAdmin 71e151d76a wayland: declare the versioned constructor wrapper under its real name 2026-10-02 13:07:12 +02:00
LibretroAdmin 7f191970bb xenon360: write_avail and buffer_size, and the write kept within its buffer
The driver queues up to the latency setting, within libxenon's 64 KiB
ring, and reports it: buffer_size is that capacity and write_avail the
capacity less what is unplayed, so the rate control runs. A write
converts and submits in passes of at most the 2048-frame staging
buffer, each no more than the room, so a long write no longer runs
past the buffer and a non-blocking one takes exactly what write_avail
reported. udelay comes from <time/time.h>.

samples/audio/xenon360 builds the driver against stand-in libxenon
headers over a scripted sound queue.
2026-10-02 13:02:06 +02:00
LibretroAdmin c8c881d740 wayland: remove the unused xdg_surface listener and unused locals 2026-10-02 12:59:05 +02:00
LibretroAdmin 16fbc490a4 wayland: bind wl_seat, wl_output and wl_compositor at newer versions
Each is bound at the lowest of what the compositor offers, what the
runtime libwayland knows and what the listeners here handle - capped
by the *_SINCE_VERSION macros of the headers built against, so older
headers and libraries keep the versions they can decode.  That is up
to seat 9, output 4 and compositor 6.

From seat v5 scrolling arrives in frames: a wheel scrolls by its
notches (axis_discrete, or value120 summed to whole notches from v8,
so a high-resolution wheel no longer scrolls on every fraction), a
touchpad by the sign of its motion as before.  Surface v6's
preferred_buffer_scale is taken over the largest output scale.  Seat
devices, the seat and outputs are released rather than only
destroyed where the bound version has a release request.
2026-10-02 12:59:00 +02:00
LibretroAdmin 46e6cd3631 switch_audio: write_avail and buffer_size count every queued buffer
The driver takes back the buffers the service has released without
waiting, so write_avail is those buffers plus the rest of the one
being filled, and buffer_size every buffer carrying its chunk. A
buffer is appended once it holds its chunk: the latency setting in
bytes, spread across the buffers. A write fills buffers in turn until
its audio is taken or none is free, which is what write_avail
reported. The unused thread, mutex and condvar shims and the append
timestamp go.

samples/audio/switch_audio runs the libnx build against a stand-in
<switch.h> whose audout service plays buffers out on the test's word,
and checks every write takes exactly the room reported.
2026-10-02 12:58:15 +02:00
LibretroAdmin c7da3ee4b0 alsa_qsa: write_avail and buffer_size span the staging and the device queue
buffer_size is the staging block plus the device queue the channel
setup reports, and write_avail what a non-blocking write takes now:
the rest of the staging block and the whole blocks the queue has room
for, from the channel status. The rate control steers the room to
half the size, which a room limited to the staging block could never
reach. The staging is one block, since snd_pcm_write copies it out.

The qsa lane's mock reports the queue in the channel status, and the
suite checks write_avail against what each write actually takes as
the device drains.
2026-10-02 12:58:11 +02:00
LibretroAdmin 495faf31cf ctr_csnd: write_avail and buffer_size are in bytes
The ring holds 2048 int16 stereo frames, and both reports now count
them in bytes of what write() takes, as the contract in audio_driver.h
asks and ctr_dsp already does: the fast-forward bound and the threaded
pipeline's passes read the room in bytes. The write's two unused
locals go.

samples/audio/ctr_csnd builds the driver against a stand-in <3ds.h>
whose system tick is the device, and checks the room shrinks by the
bytes written and grows by the bytes played.
2026-10-02 12:58:08 +02:00
libretroadmin 5eca997fa5 Core updater: Update Installed Cores fetches its installed cores alone
core_updater_list_parse_network_take() takes a flags mask, and with
CORE_UPDATER_LIST_PARSE_INSTALLED_ONLY the parse leaves out every core
not installed in the libretro directory before reading its info file.
Update Installed Cores' list fetch passes it, so a run reads core info
for the installed cores only rather than for the whole buildbot
listing; the Core Downloader still gets the full list. A listing with
any well-formed line types the list as a buildbot list even when no
core in it is installed, and the update task reads success from that
type, so a fetch that finds nothing installed still ends in 'all cores
updated' and a failed fetch in 'core list failed'.

update_installed_scan_test counts core info reads at the link boundary
in the scan lanes, and gains status lanes for an empty libretro
directory and an unreachable buildbot, threaded and not.
2026-10-02 12:37:56 +02:00
LibretroAdmin 68d7089d16 libretro-common: retro_atomic_exchange_int on the volatile fallback
The fallback backend now offers exchange_int as a plain read-then-write,
alongside its cas_int and with the same single-core caveat, so
queues/retro_triple_buffer.h builds on every backend. The forced-backend
loop in the gfx samples workflow covers it.
2026-10-02 12:30:18 +02:00
LibretroAdmin 579e1264ee apple: the legacy iOS and PPC projects build the built-in TLS client
The iOS 6 through 11 projects and the PowerPC project set
HAVE_NETWORKING with no TLS backend, so HTTPS was unavailable in them.
Define HAVE_SSL, HAVE_CRYPTO and HAVE_RETROSSL alongside it. The
client uses only the int atomics every retro_atomic backend provides,
including the volatile fallback the Xcode 3 GCC gets.
2026-10-02 12:28:26 +02:00
LibretroAdmin d75acbda28 apple: the OSX 10.7 project builds the built-in TLS client
Its project-level Debug and Release flags carry HAVE_SSL without a
backend, so griffin takes the system mbedTLS socket layer, which is
no longer bundled. Define HAVE_RETROSSL and the HAVE_CRYPTO it needs.
2026-10-02 12:25:09 +02:00
LibretroAdmin 0b48f128d7 apple: the OpenGL project builds the built-in TLS client
The project-level Debug and Release flags carry HAVE_SSL without a
backend, so griffin falls through to the system mbedTLS socket layer,
which the macOS runner does not have. Define HAVE_RETROSSL and the
HAVE_CRYPTO it needs, as BaseConfig.xcconfig does for the other
Apple builds.
2026-10-02 12:18:40 +02:00
LibretroAdmin 744ed52a6c wayland: remove unused xdg_surface configure handler 2026-10-02 11:36:45 +02:00
LibretroAdmin 82fd9a0f3c Hook up drag and drop to the menu on X11, Wayland, Win32 and macOS
The drag and drop payload becomes a string_list, so one drop can carry
several files.  menu_driver_drop() takes a drop from any thread and
hands it to the selected entry's action_drop on the next menu frame;
it refuses while the menu is closed.  X11 (as an XDND target), Wayland
and Cocoa drops go there, and Win32 tries it before its existing
load-content path.

The Load Content entry binds action_drop: the first dropped file is
loaded with core detection, as when it is picked in the file browser.
2026-10-02 11:36:42 +02:00
LibretroAdmin 247ff38a18 libretro-common: move the percent decoder into stdstring
string_percent_decode() is net_http_urldecode() without the networking
dependency, so builds without HAVE_NETWORKING can decode file:// URIs.
net_http_urldecode() and net_http_urldecode_inplace() now call it.
2026-10-02 11:36:38 +02:00
LibretroAdmin 67f6344805 apple: keep a haptic player running between rumble levels
A core does not hold a rumble level: it sets one and clears it, and
some do both every frame. A Game Boy rumble cartridge switches its
motor on and off many times a second, and the core reports that as it
happens.

The MFi driver stopped its CoreHaptics player whenever the level was
zero and started it again at the next level - and started it again on
every level after that, playing or not. Each start begins the effect
from rest, so with a core that pulses, the haptic engine never reached
strength: the rumble was weak and uneven. On an iPhone, SameBoy's
rumble is much weaker than the standalone app's (issue #18494).

Keep the player playing and change its intensity:

- a level above zero sets the intensity, and starts the player only if
  it is not already playing;
- a level of zero turns the intensity down to nothing and leaves the
  player playing;
- once the level has stayed at zero for a quarter of a second, the
  poll stops the player. A flag keeps the poll from looking at the
  players at all unless one is waiting to be stopped.

The same applies to the phone's own engine and to a controller's
strong and weak players. The playback state is forgotten wherever the
players are: when an engine stops or resets, when CoreHaptics throws
because its engine is not running, and on shutdown.

Not built or run: this was written without an Apple toolchain. The
new functions were syntax-checked on their own against stand-in
declarations; the file as a whole has not been compiled.
2026-10-02 11:15:38 +02:00
LibretroAdmin 3e521ec8f7 hw_context_mirror_test: NONE counts only once every set is torn down
The next SET_HW_RENDER fills the struct before it publishes its type,
so a reader that saw NONE can be looking at that fill. Sets begun and
teardowns published are counted: the reader checks the struct only
while the two are equal and discounts a dirty byte if a set began
since. Releasing NONE before the memset still fails it.
2026-10-02 11:15:12 +02:00
LibretroAdmin 5a8e2c16b0 tpool_parallel_test: the spells' result sink is an atomic
Every job stores into it, from every worker; as a plain variable that
is a race TSan reports in the test itself.
2026-10-02 11:15:09 +02:00
LibretroAdmin e4fd9f91fd gfx_widgets: a muted task spawns no widget
A task widget's text is its title, so a push with no message still
spawns one for a task that has a title. What spawns nothing is a muted
task: its only push is the retirement of a widget it may have had.
2026-10-02 11:15:06 +02:00
LibretroAdmin ac1b9c3dfa task_queue: a finished task always makes its retirement push
Whether a widget is attached is read by the widgets, under their state
lock: frontend_userdata is their link, and under the threaded video
wrapper the video thread clears it as it frees a widget. The gather
pushes every finished task, and the widgets spawn nothing for an empty
push with nothing attached.

gfx_widgets_task_gate_test retires tasks through the widgets' push
while another thread frees the widgets bound to them, under TSan and
ASan, and checks a muted task's retirement puts nothing up.
2026-10-02 10:20:47 +02:00
libretroadmin 0a27bfd955 Core backup: replace the core only once a restore is complete
A core restore writes a temporary beside the core and renames it over
the core once every byte is written, so a restore that stops part-way -
a write error, a full disk, the task cancelled on quit - leaves the
installed core as it was and removes the partial temporary. A core
reached through a symbolic link is resolved first, so the link stays and
its target is replaced, as a write through the link did before. A Play
Feature Delivery install that finds its core missing and the <core>.bak
an interrupted install moved it to takes that file as its own backup,
restored if the install fails and dropped if it succeeds; an install
Play Feature Delivery already completed drops it as well.

samples/tasks/core_backup/core_backup_io_test stops a restore one
quantum in by cancelling it and by failing its writes, and restores
through a symbolic link; samples/tasks/core_updater/pfd_switch_cores_test
leaves an interrupted install's backup for each install outcome.
2026-10-02 10:19:30 +02:00
LibretroAdmin fdc3a59c86 audio_mixer: a voice's volume and gain are atomics
Setting or reading either takes no lock: volume holds the float's bits
and gain the Q16.16 value, stored and loaded atomically, and the mix
reads them the same way.

audio_mixer_voice_threads_test sets volume and gain on one thread,
reads them on a second and mixes the voice on a third, under TSan.
2026-10-02 10:06:40 +02:00
LibretroAdmin 3ff20e48e2 video_thread: the menu texture and state flags reach the worker without a lock
The menu texture goes through a triple buffer: set_texture_frame()
copies into the back slot and publishes it, and the worker's apply
takes the newest with retro_triple_buffer_take(), which hands out a
frame once per publish. The enable and full-screen flags are one
atomic word and a state apply is an atomic flag the worker exchanges
away, so frame.lock goes and video_thread_loop's lock budget is 2.

retro_triple_buffer_test covers take().
2026-10-02 10:06:37 +02:00
LibretroAdmin bd35ea5496 task_database: task_push_dbscan takes the path and the callback only
The scan reads its playlist and database directories and the
hidden-files setting from the settings, and treats the path the same
whether it names a directory or a file, so task_push_dbscan() takes
only the path and the callback. companion_core_request_scan() takes
only the core and the path, and companion_core_pref_show_hidden_files()
is gone with its last callers.
2026-10-02 09:46:53 +02:00
libretroadmin ecbdea42dd Core updater: switch PFD cores on each install's callback
The Play Feature Delivery 'switch installed cores' task learns of each
install's completion, and the error it ended with, from the install
task's callback through the completion record the other core updater
waits use, which now also carries the sub-task's error; it no longer
holds the install's retro_task_t, which the queue frees as it retires
it. The install finder no longer counts an install that has finished
but not yet been retired, so the next install can be pushed as soon as
the previous one reports completion instead of being refused and its
core skipped.

samples/tasks/core_updater/pfd_switch_cores_test runs the shipping
switch and install tasks, built with ANDROID defined against stand-ins
for the play_feature_delivery_* calls: every core switched, a failed
install ending the switch, and the switch cancelled while an install
runs, each threaded and unthreaded, under ASan, UBSan and TSan in
Linux-samples-tasks.yml.
2026-10-02 09:45:51 +02:00
LibretroAdmin 19a164a59d camera/ffmpeg: frames go to the core through a triple buffer
A poll hands the core the newest finished frame, and the decode
thread fills the next one while the core reads, however long the
core takes. Each turn releases the packet av_read_frame() filled, and
stop() frees the conversion planes start() allocated.

ffmpeg_live_test adds a counting source whose last frame a poll must
show, and a 300 ms frame callback the thread must keep decoding
through; its ASan lane checks for leaks.
2026-10-02 09:42:11 +02:00
LibretroAdmin 618f83c50e libretro-common: queues/retro_triple_buffer.h
The HUB75 driver's three-canvas handoff, as a libretro-common queue
any producer and consumer can share: one exchange hands a frame over,
another picks up the newest, and neither side waits. Its test moves
with it to libretro-common/samples/queues.
2026-10-02 09:42:07 +02:00
LibretroAdmin 55e683c66e rtime: localtime() is guarded by an atomic flag
The flag needs no setup, so rtime_localtime() is guarded before
rtime_init() and after rtime_deinit() as well as between them, and no
caller is handed another thread's fields. Without CAS the lock stays.

rtime_localtime_test converts four timestamps from four threads at
once in all three windows and checks each thread gets its own.
2026-10-02 09:42:04 +02:00
LibretroAdmin 4780632d76 crypto/x509: a chain walk runs over its own reference to the store
The trust store is built with no lock held and published whole; a
walk takes a reference and verifies the chain with nothing held, and
a reload swaps in a new store, leaving the old one to whoever drops
the last reference. The flag guarding the swap and the references
covers a few loads and stores, so it needs no lock object made on
first use.

crypto-tsan-check also runs x509_store_threads_test under TSan and
ASan: four threads load the store at once and verify against it, then
keep verifying while a fifth swaps between two stores and none.
2026-10-02 09:42:00 +02:00
LibretroAdmin 394ef6dd4c mmdevice: the open-endpoint table is guarded by an atomic flag
The render and capture threads can both be first to record an
endpoint, and the notification thread can be first to read one, so
the table has no lock to create on first use: a CAS flag guards a
compare or a copy, and the old id is freed outside it.

samples/audio/mmdevice_active sets the table from two threads and
matches from a third under ASan and TSan.
2026-10-02 09:22:46 +02:00
LibretroAdmin 5c2943f538 librsound: the stream is fed by the callback alone
The driver always sets a callback, so the blocking interface goes:
rsd_write and its FIFO, the blocking thread with the cond it slept on,
rsd_exec, rsd_pause, rsd_simple_start, rsd_pointer, rsd_get_avail and
rsd_samplesize.
2026-10-02 09:22:43 +02:00
LibretroAdmin 6a2717b436 crypto/pk: the curve constants are computed by whoever claims them
The first caller to move the curve's ready word from 0 to busy
computes the constants; any other yields until it reads ready. There is
no lock to create on first use, and none that can fail to be created.

The concurrent first-use lane in the tls_retro matrix also runs
against an ECDSA server, so six threads initialise P-256 at once.
crypto/bench and smb_retro gain all targets, so the libretro-common
link check builds them.
2026-10-02 09:22:39 +02:00
LibretroAdmin 7d1db3427a gx: drop the device mutex only the device thread ever took 2026-10-02 09:22:36 +02:00
libretroadmin 93008eb606 Core updater: parse the core list in slices, publish it on the main thread
The get-list task parses the buildbot listing one line per work item of
the shared I/O window, into a list private to the task, and its callback
swaps that list into the caller's on the main thread. A listing line
costs a stat and a core info read, so the parse no longer lands on one
frame, and the Core Downloader's menu callbacks, which read the cached
list every frame, only ever see its old contents or the complete new
ones; a failed, cancelled or unfinished fetch publishes an empty list.
core_updater_list_parse_network_take() takes the response body without
copying it.

update_installed_scan_test gains a get-list lane that forces the window
to its one-item floor and asserts a gather per listing line, with the
caller's list never seen emptied or half-built.
2026-10-02 09:21:10 +02:00
LibretroAdmin c716770c41 thread_read_audit: main-thread task handlers are not worker entries
A handler assigned in a function that also sets
RETRO_TASK_FLG_MAIN_THREAD on its task runs on the main thread, so the
audit no longer walks it as a worker entry. The handler stays an entry
when its file ever clears that flag, or when the same handler is also
assigned without it. The summary line counts the handlers set aside.

The self-test gains a main-thread task, a pool task and a task whose
file clears the flag again.
2026-10-02 09:12:42 +02:00
LibretroAdmin 902c7f9a57 task_database: pass over every refused database in one scanner step
The core-claim gate now advances past every database it refuses in a
single step, so a file costs a handler call per database it can match
plus a fixed few, rather than one per database in the directory. On
the regular task queue the handler runs once per check, which is once
a frame.

database_scan_test scans four unmatched files over one claimed and 48
unclaimed databases on the regular queue and bounds the checks the
scan takes by the file count.
2026-10-02 09:12:39 +02:00
LibretroAdmin 34383c16b2 net/tls: the session cache is guarded by an atomic flag
The sections it guards only look up and copy, so a CAS flag does the
job with no lock object to create on first use, and none that can fail
to be created.

tls_retro gains an all target that builds every tool, so the
libretro-common link check covers the threaded build.
2026-10-02 09:12:17 +02:00
LibretroAdmin b79a00fc8d play feature delivery: a core download asks Java with the status lock released
The download is claimed under status_lock and Java is asked after it
is released. The status callbacks take the same lock, so one answered
on the calling thread deadlocked it against itself, and one delivered
on Java's thread waited out the whole call. play_feature_delivery_test
builds the file on the host against the Android stubs, with a Java
that reports the install back inside the call.
2026-10-02 09:12:14 +02:00
LibretroAdmin 847990ebae ffmpeg core: the wait for a finished video slot ends with the decode thread
The decode thread ends at the end of the file, and the main thread can
be waiting on the tail slot when it does - a slot nothing will finish
now. The wait also ends once the thread is gone, and the thread's exit
wakes it under the buffer's lock along with the fifo's waits.
ffmpeg_finished_wait_test parks the wait, ends the thread and checks
it returns.
2026-10-02 09:12:11 +02:00
libretroadmin 2253bec061 Core updater: build the decompress callback only with HAVE_COMPRESSION
cb_decompress_task_core_updater_download() is pushed only by the
archive branch of the download's HTTP callback, which exists only with
HAVE_COMPRESSION, so it is compiled under the same guard and builds
without compression no longer warn that it is unused.
2026-10-02 09:11:00 +02:00
LibretroAdmin 3a91259658 menu: add drag and drop cbs actions
Adds action_drag and action_drop to menu_file_list_cbs_t, bound per
entry from menu_cbs_drag.c / menu_cbs_drop.c like the other actions.
Both receive the dragged item as a local path and return 0 to accept
it; action_drag reports a hover only.

menu_entry_drag() and menu_entry_drop() dispatch to an entry of the
current list, so platform drag and drop code can hand a payload to
whichever entry it lands on. No entry binds them yet.
2026-10-02 08:58:57 +02:00
LibretroAdmin 32e5636375 pipeline_clocked: a stall run held off by the host is retried, not failed
Besides the core's own wakes, the lane now times the device's wakes, a
canary thread's on the same cores, and how long the pipeline's consumer
takes to answer the oldest unanswered frame; any of them past a quarter
frame marks the run inconclusive and it is retried, up to ten times.
A run the host left alone is judged as before.
2026-10-02 08:57:56 +02:00
LibretroAdmin 0903d2dec5 audio: a mixer stream's sound is built without the state lock
audio_driver_mixer_add_stream() checks its parameters, and under the
lock only that the mixer is up and which sample format to build in;
the sound is then built - a WAV converted and resampled whole - with
the lock free, and the lock is taken again to claim the slot and start
the voice. The audio thread mixes on through a load instead of waiting
it out. mixer_lock_test checks no sound is built with the lock held.
2026-10-02 08:57:53 +02:00
LibretroAdmin e2e0459d69 companion: the browser's worker handoff is two atomics
The generation an open supersedes a running enumeration with is an
atomic the UI thread writes and the worker reads, now between every
entry, so the join a new open makes returns after one stat at most;
the job's done flag is a release store after everything it covers.
The worker reads its job once, as created. browse_lock is gone.
2026-10-02 08:57:49 +02:00
LibretroAdmin 8acbe4ab8c core_info: resolve database claims once per scan
Which cores claim a database does not depend on the file being
scanned, so the scanner now asks core info once per database when it
sizes its per-database state: core_info_database_claim() returns the
merged extensions of the claiming cores and whether any of them
matches archive members. The per-file test is a single extension
lookup in that list, with no core info lock taken, and the claims
move with their database when a match promotes it to the front.

database_scan_test gains a second core claiming its own database with
its own extension, so claims differ per database: a claim left behind
by the promotion, or a claim that ignores the database, fails it.
2026-10-02 08:56:56 +02:00
libretroadmin 861bd6a089 Core updater: keep task handles alive until their sub-tasks retire
A core updater task can be cancelled - every task is, on the exit path's
drain - while a task it pushed is still running, and that sub-task's
callback reaches back into the waiter's handle when it retires. Each
handle a callback can reach is now reference-counted: the list and
download handles hold one reference for the task and one for each pushed
HTTP or decompress callback, and Update Installed Cores waits on its list
fetch and downloads through the shared completion record the backup wait
already uses. A cancelled waiter drops its reference and the last one
frees; the core list a pending fetch still parses into goes with the
record. A failed sub-task push drops the callback's reference and frees
its transfer object, and a download's HTTP callback reports a transfer
without data as failed instead of never signalling.

samples/tasks/core_updater/update_installed_scan_test gains cancel lanes,
threaded and unthreaded, that retire the parent while the loopback server
holds a child's response: task_queue_reset() with the list fetch in
flight, and the parent alone cancelled in WAIT_LIST and in WAIT_DOWNLOAD.
They run under ASan, UBSan and TSan in Linux-samples-tasks.yml.
2026-10-02 08:44:09 +02:00
Anthony SchemelandClaude Opus 5.5 8732419349 core_info: cheaper core match in the database scanner
core_info_database_supports_content_path() runs once per scanned file
per database, and its loop runs once per installed core. It recomputed
the file's extension inside that loop, and it checked each core's
extension list before its much shorter database list.

Compute the extension once before the loop, and check the database list
first. Both checks must pass, so the result is unchanged.

Measured on a scan of 1522 files (NES, SNES, GBA, Mega Drive) against
145 databases with 301 core info files, four alternating runs each:
CPU time 1.66-2.17 s before, 0.87-1.01 s after. The playlists written
are byte-identical.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-02 08:45:42 +02:00
LibretroAdmin f8346c53dd win32: look for controllers again when the system wakes up
Nothing in the Windows input code reacted to the system sleeping and
waking. Controllers are only looked for again on a device-change
notification, and a controller can come back from sleep with its
handle dead and no such notification: a DirectInput pad then reports
itself lost, is dropped, and stays gone until it is replugged or
RetroArch is restarted (issue #17934).

On PBT_APMRESUMEAUTOMATIC, arm the hotplug settle timer that a device
change arms. The joypad driver is then reinitialised once, a moment
after the wake-up, by whichever input driver owns the window - the
same recovery a replug triggers, without the replug. A device that
takes longer to come back still announces itself with a device change
of its own.

The message is routed to the shared window procedure from all three
window procedures. Where no input driver handles the timer, the
existing code stops it, so nothing is left ticking.

Compiled with mingw-w64. Not run through a real sleep and resume.
2026-10-02 08:25:25 +02:00
libretroadmin fa36171d92 Core updater: make the update-installed child completion flags atomic
The list and download completion flags of the 'update installed cores'
task are set by the child tasks' callbacks, which run on the main thread
when the child is retired, and are polled by the parent's handler on the
task worker.  They are now retro_atomic_int_t: a release store in the
callback and an acquire load in the handler, which also publishes the
core list the list task filled in before the parent reads its size.

This clears the ThreadSanitizer data race the threaded lane of
samples/tasks/core_updater/update_installed_scan_test reports in
Linux-samples-tasks.yml.
2026-10-02 08:22:23 +02:00
LibretroAdmin f953aaae96 android: a TV remote over HDMI-CEC is not a gamepad
Android turns a television's CEC commands into key events - the
arrows, Center and Back - with no input device behind them: device id
-1, source AINPUT_SOURCE_HDMI. The driver took an event from an
unknown device that is not a keyboard for a new gamepad, and Android
names device -1 "Virtual". So the remote became an unconfigured pad
called "Virtual" on the next free port: its keys did nothing, and it
held a port a controller should have had (issue #19249). Only the
NVIDIA SHIELD had a mapping for it.

Tell the remote by the event's source instead of by that name, which
everything synthetic shares, and handle it the way a keyboard is
handled: its keys go to the keyboard row and it gets no pad slot. On
that row the arrows are already arrows, Center is already folded into
Enter and Back is already the remote's back, so the menu can be driven
with it and no profile is needed.

The SHIELD keeps its own handling: it maps CEC input onto its
"SHIELD Virtual Controller" pad, with an autoconfig profile to match.

The source test lives in android_kbd_route.h, which takes no NDK
types. samples/input/android_cec_remote builds it on a host and pins
that a CEC key registers no pad, whether it is the first event seen
or follows a controller; that it lands on the keyboard row; that only
the HDMI source is taken for a remote; that a controller, one
reporting id -1 included, still gets its pad; and that the SHIELD is
left alone. Ignoring the source, as the driver did, is put back by a
sabotage mode and asserted to be caught.

Compiled with the NDK through the Android unity build. Not run with a
television: it needs a set with HDMI-CEC.
2026-10-02 08:06:49 +02:00
libretroadmin fc8585537c Core updater: learn of backup completion from its callback
task_push_core_backup() takes a completion callback and user data, run
from the backup task's own callback at retirement. The download task
waits on its automatic backup through a two-reference completion record
shared with that callback: the handler reads only the record's flag,
which is set before the backup task is freed, and copies progress
through the same find-based probe the HTTP wait uses. A download
cancelled mid-backup drops its reference and the callback frees the
record when it runs.

samples/tasks/core_updater/download_backup_wait_test retires and frees
the backup between two download ticks, and cancels a download while
its backup runs; it runs in Linux-samples-tasks.yml under ASan, UBSan
and TSan.
2026-10-02 08:02:33 +02:00
libretroadmin 79055940d7 Core backup: release the task handle at retirement
The backup and restore tasks keep their handle on task->state until
task_core_backup_cleanup() frees it at retirement, since
task_core_backup_finder() reads core_path through it for as long as the
task is findable, including the stretch after the worker has finished
it. The handler closes the files and drops the buffers as it finishes,
so the callback still sees the core file closed.

core_backup_io_test gains a threaded lane that pushes a duplicate
backup while the first is finished but not yet retired: the push must
be refused then and accepted once the first has retired.
2026-10-02 08:02:30 +02:00
LibretroAdmin a13be43ecd video/thread: the async upload lists are mpsc stacks
Any thread posts an upload to the in stack and the video thread takes
it whole; the video thread puts each finished node on the out stack
and the main thread takes that whole, each reversed into posting
order. No post, take or put takes the wrapper's lock, which a node's
link now never needs; on the volatile atomic backend, which has no
pointer atomics, a push and a take are made under it instead.

The lock budget holds video_thread_loop to three acquisitions.
2026-10-02 07:25:46 +02:00
LibretroAdmin 2e10a65f98 task_queue: progress is reported after running_lock is dropped
The gather's pass under running_lock counts the due main-thread tasks
and takes down the running ones; the progress reports - formatting,
and the push into the frontend's message path - are made after it, so
a worker picking up or settling a task, or a thread queueing one,
never waits on the frontend. The list the tasks are taken down in
grows on the main thread to the number running.

task_queue_snapshot_test queues a task from a second thread inside a
progress report, and checks every one of 80 running tasks is reported.
2026-10-02 07:25:43 +02:00
LibretroAdmin d78aefabd8 android: the removed-controller hand-off in a header a host test can build
The mailbox that carries a removed device's id from the listener to
the input thread, and the rule that gives a returning pad its old
port, were inside android_input.c, which only builds against the NDK.
Neither has run on a device yet, and nothing could run them off one.

Move both into android_pad_removed.h, free of NDK types, the way
android_kbd_route.h holds the keyboard routing: the mailbox as
android_removed_post() and android_removed_take(), the pad table as
android_pad_mark_removed() and android_pad_find_removed(), with
state_device_t alongside. The driver calls them where the code was;
its behaviour is unchanged.

samples/input/android_pad_removed builds the header on a host and
pins:

- no removal is lost and none is delivered twice, with three threads
  posting 6000 ids while the input thread polls (the box is full for
  about a third of the posts, so the refusal path is under load too);
- a full box refuses an id and keeps the ones it holds, ids that are
  not a physical device's are refused, and id 0 survives;
- a returning pad gets its old port back, and of two identical pads
  it is the removed one's slot it takes.

Emptying a removed slot's name, as the first version of the change
did, is put back by a sabotage mode and asserted to be caught. Runs
under ASan + UBSan and under TSan.

Compiled with the NDK through the Android unity build.
2026-10-02 07:11:52 +02:00
LibretroAdmin fefafd4f67 udev: the not-a-joystick rules in a shared header, with a test
The rules that keep keyboards, mice and their receivers from being
taken for controllers lived inside udev_joypad.c. Move them to
input/common/linux_not_joystick.h, which takes no libudev or evdev
types, so any Linux joypad driver can use the one rule set and a test
can build it on its own. The udev driver calls it as before; nothing
changes in what it accepts.

samples/input/linux_not_joystick pins issue #19674: the Microsoft
2.4GHz Transceiver whose "System Control" node took port 1 is not a
controller, nor is that node or "Consumer Control" on a device the
list has never seen, nor a listed device with no name at all. Real
controllers are kept, including a pad whose product name says
"Wireless", a pedal set with axes and no buttons, and a controller
that merely has the words in its name. The list is checked for
duplicates.
2026-10-02 07:10:14 +02:00
LibretroAdmin 3c47665b3b android: the pen mapping in a header a host test can build
How a pen's events become pointer and mouse state was decided inside
the motion handler of android_input.c, which only builds against the
NDK, so nothing could run it off a device - and nothing has yet run
it on one.

Move the decision into android_stylus_map.h, free of NDK types, the
way android_kbd_route.h holds the keyboard routing. The driver turns
a motion event into one of five pen events, calls
android_stylus_map() and applies the result: whether to write the
position, the right mouse button, and the pointer count to set or
leave. The hover guard and proximity timing stay in the driver. No
behaviour changes.

samples/input/android_stylus_map builds the header on a host and
pins the mapping:

- a pen is one touch: over 200000 events under random settings, none
  asks for a pointer count above 1;
- the tip pressing is pointer 0 pressed, lifting releases it, and a
  tip below the pressure threshold moves without pressing;
- a hover does not release a finger, and a finger lifting does not
  release a pressing pen;
- the barrel button is the right mouse button, and is let go when
  the pen leaves range or its gesture is cancelled;
- with contact not required, the barrel button presses while
  hovering.

The first version's pointer counts - 2 for the tip, 3 with the barrel
button - are put back by a sabotage mode and asserted to be caught.

Compiled with the NDK through the Android unity build.
2026-10-02 07:10:10 +02:00
LibretroAdmin 297d5374f5 CI: a mouse in the menu - left click, right click, wheel, movement
After the pen support was merged, a left click in the menu was thrown
away and nothing in CI noticed: it gave every pointer that is not a
touchscreen no gesture on release, and a mouse's left click reaches the menu
drivers as a tap gesture. Moving the pointer, the wheel and the right
button take other paths and kept working. It was in shared menu code,
so every platform and every menu driver was affected, and no CI job
clicks anything.

samples/menu/mouse_click links the shipping objects with only main()
replaced, like the other menu harnesses, and drives the real frame
loop with a scripted mouse. It finds the Settings row by asking the
menu which entry is under the pointer, so the layout stays the menu
driver's business, then:

- opens Settings with a left click;
- scrolls its list with the wheel, down and up;
- goes back with a right click;
- checks that a press held for two seconds is not a click.

The null video driver has no viewport and never draws a menu frame,
and RGUI reads the pointer while drawing, so the harness's video
driver supplies both the way a real one does.

With the removed check put back, the harness fails on the left click
and on nothing else, which is the bug as it was reported. It runs in
the menu harness workflow, on every change under menu/.
2026-10-02 07:10:06 +02:00
LibretroAdminandf4mrfaux cf61e93c6c android: report a controller the OS has removed as disconnected
Android tells the activity when an input device goes away - a pad
switched off, out of range or unplugged - but nothing passed that on
to the input driver, and no input event marks a removal. A pad
therefore stayed connected for as long as the app ran:

- its port kept its device name, so an overlay hidden while a
  controller is connected never came back;
- whatever it was holding stayed held, since no release arrives from a
  device that is gone;
- no disconnect notification was shown, and pausing on disconnect
  could not happen.

RetroActivityCommon.onInputDeviceRemoved now hands the device id to
the driver. It is left in a small lock-free mailbox and taken out at
the next input poll, which clears the pad's key, axis and hat state
and reports the disconnect through input_autoconfigure_disconnect, as
the other joypad drivers do.

The pad's slot keeps its name and is marked removed instead of being
emptied, so the device gets its old port back when it returns, under
whatever id Android gives it; its return is reported as a connect. A
removed slot is matched before a live one of the same name, so of two
identical controllers the one that went away is not taken for the one
still connected.

A removal is read at the next poll, which can be long after it
happened if the app was in the background. If the id resolves again by
then, the device has come back under it and nothing is reported.

This is the device-removal part of PR #19077, reworked. That version
emptied the slot's name, which the driver uses to find a returning
device's port: every reconnect would have moved the pad to the next
free port.

Compiled with the NDK through the Android unity build. Not run on a
device.

Co-authored-by: f4mrfaux <[email protected]>
2026-10-02 07:12:54 +02:00
LibretroAdmin 0bdc84c68d ci: runloop_iterate's size reference follows the runner's compiler
The ubuntu-24.04 image's GCC lays runloop_iterate out at 5004 bytes
for the same source the reference was taken from; the reference is
that measurement, so the budget is again a quarter over it.
2026-10-02 07:05:13 +02:00
LibretroAdmin dbb607f0aa coremidi: the input queue between the receive thread and read() takes no lock
Each side owns one index of the ring and publishes it with release
ordering; the slot one behind the read index is never written, so the
event read() hands out stays intact until the next read. A clear from
the notification thread or set_input() is a flag the next read acts
on, since only the reader moves the read index.
2026-10-02 07:05:09 +02:00
LibretroAdmin 017a7b45bf record/ffmpeg: pushes and the encoder sleep on eventcounts
The encoder waits on data, which each push notifies after writing; a
push that finds no room waits on space, which the encoder notifies
after each read, bounded so it looks at alive again. No lock is taken
on either side and no wake can fall between a check and a sleep.
deinit_thread() notifies both once alive is clear.

The lock budget holds ffmpeg_push_video, ffmpeg_push_audio and
ffmpeg_thread to none.
2026-10-02 07:05:05 +02:00
LibretroAdmin 1f7df61884 hub75: frames reach the refresh thread through three canvases
The video thread draws into a canvas of its own and hands it over with
one atomic exchange; each panel scan picks up the newest finished frame
with another. Neither thread waits for the other, so a frame no longer
waits out a scan of the chain, and the refresh loop's stop flag is an
atomic.
2026-10-02 07:05:02 +02:00
LibretroAdmin 183cfaffce sdl3 audio: the device thread wakes waiters without a lock
The stream callbacks and the removal watch notify an eventcount; a
blocked write, write-avail or capture read registers on it before it
looks at the stream again, so a callback is neither missed nor made to
wait on the writer for a mutex. Builds without threads look again each
millisecond up to the same bounds.
2026-10-02 07:04:58 +02:00
LibretroAdmin c17f003ced tpool: finished work is retired without the pool lock
The pool lock guards the queue alone. The thread that finishes an item
retires it on the outstanding count, and the one that retires the last
wakes tpool_wait() through an eventcount; teardown still waits for the
threads under the lock, which is the last thing each of them touches.
On the volatile atomic backend the count is retired under the lock its
increments are made under.

Every build that compiles tpool.c now compiles retro_eventcount.c too,
and the lock budget holds tpool_worker and tpool_help to one
acquisition each, the dequeue.
2026-10-02 07:04:54 +02:00
libretroadmin 490359e8dc Core updater: pace the update-installed scan with the shared I/O window
Update Installed Cores now walks the buildbot list and hashes installed
cores inside one task_nbio_slice window per handler call: every list
entry and every 256 KB CRC chunk is a work item, so entries that are
not installed cost a stat each rather than a tick each, and core
hashing shares the per-frame window with the other budgeted tasks
instead of carrying a private 4 ms deadline. The per-core lock check
runs once on reaching a core, and a CRC resumed on a later tick goes
straight back to hashing. The download task's CRC uses the same window.

samples/tasks/core_updater/update_installed_scan_test drives the real
task over a 512-entry loopback index on both queues, asserting the
unthreaded scan's gather count and one path_is_valid() per entry plus
one per installed core; it runs in Linux-samples-tasks.yml under ASan,
UBSan and TSan.
2026-10-02 06:53:07 +02:00
LibretroAdmin 2933b0bcf1 menu: a mouse's left click selects again
Since the S-Pen support was merged (4592b490), releasing the left
mouse button in the menu did nothing, in every menu driver. Moving the
pointer, the wheel and the right button still worked.

That merge changed the pointer-release code to give every pointer
that is not a touchscreen no gesture at all. A mouse is such a
pointer, and its left click reaches the menu drivers as a tap gesture,
so the click was thrown away. The right button, the wheel and motion
take other paths.

Restore the gesture detection for all pointer types, as it was before
the merge. The stylus does not need the exclusion: an Android pen
reaches the menu as a touchscreen pointer, which the removed check did
not touch.
2026-10-02 06:46:49 +02:00
LibretroAdmin 036de6704f udev: ignore devices tagged as joysticks that are not controllers
udev marks some keyboards, mice, tablets and their wireless receivers
as joysticks, because their HID descriptor declares joystick-like
axes. The udev joypad driver took every ID_INPUT_JOYSTICK device at
its word, so such a device was given a port of its own. It is
enumerated before the real controller often enough to take port 1 and
push the controller to port 2 - and the menu listens to port 1 by
default, so the controller appears not to work at all.

Issue #19674 is this with a Microsoft 2.4GHz Transceiver v8.0
(045e:0745), the receiver of a wireless keyboard and mouse: its
"System Control" node became pad 0 with no autoconfig, and the Xbox
360 controller was configured on port 2.

Skip two kinds of device when adding a pad:

- A node whose name ends in "System Control" or "Consumer Control".
  The kernel names each HID application collection after what it is,
  and those two are the power/sleep and media keys of a keyboard or
  receiver; no controller is either.
- A device on the list of known non-joysticks. It is the list SDL
  uses (initial_blacklist_devices), which comes from the
  udev-joystick-blacklist project, and it covers devices whose main
  node is mistagged: Microsoft and A4Tech keyboards and mice, Wacom
  tablets and others.

An ignored device takes no slot, is logged once, and its descriptor is
closed.
2026-10-02 06:41:25 +02:00
11082 changed files with 1222040 additions and 1082570 deletions

No files matched your search

+17 -1
View File
@@ -3,7 +3,23 @@ name: CI 3DS
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/3DS.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/3DS.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -23,7 +39,7 @@ jobs:
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-devkitpro:latest
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: taiki-e/checkout-action@v1
+196 -1
View File
@@ -3,7 +3,19 @@ name: CI Android
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Android.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Android.yml'
# ci_paths: end
workflow_dispatch:
repository_dispatch:
types: [run_build]
@@ -26,18 +38,45 @@ jobs:
steps:
- uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Set up JDK 25
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'temurin'
cache: 'gradle'
- name: Compile RA
run: |
cd pkg/android/phoenix
./gradlew assembleDebug
# A repository that answers with a server error is retried with
# a backoff (2 s doubling, six tries) before Gradle gives up on
# it for the rest of the build. A build that still fails to fetch
# a dependency is run again, keeping what it already fetched and
# built; any other failure fails the step at once.
set -o pipefail
for i in 1 2 3; do
./gradlew assembleDebug \
-Dorg.gradle.internal.repository.max.tentatives=6 \
-Dorg.gradle.internal.repository.initial.backoff=2000 \
2>&1 | tee gradle.log && break
grep -qE 'Could not (resolve|GET|HEAD|download)' gradle.log \
&& [ $i != 3 ] || exit 1
echo "dependency fetch failed (attempt $i); retrying in 30s"
sleep 30
done
find . -iname "*.apk" -exec ls -l "{}" \;
- name: Verify 64-bit APK alignment
run: |
set -eu
for apk in pkg/android/phoenix/build/outputs/apk/{aarch64,quest}/debug/*.apk; do
python3 tools/android_elf_alignment.py "$apk"
"$ANDROID_HOME/build-tools/36.0.0/zipalign" -c -P 16 4 "$apk"
done
- name: Get short SHA
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
@@ -48,3 +87,159 @@ jobs:
path: |
pkg/android/phoenix/build/outputs/apk/normal/debug/phoenix-normal-debug.apk
pkg/android/phoenix/build/outputs/apk/aarch64/debug/phoenix-aarch64-debug.apk
pkg/android/phoenix/build/outputs/apk/quest/debug/phoenix-quest-debug.apk
vp9-stack-abi:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- name: Use the Android project NDK version
run: |
set -eu
version=$(sed -n 's/.*ndkVersion "\([^"]*\)".*/\1/p' pkg/android/phoenix/build.gradle)
test -n "$version"
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" "ndk;$version"
echo "MEDIA_NDK=$ANDROID_HOME/ndk/$version" >> "$GITHUB_ENV"
- name: VP9 C89 stack ceiling on both Android ABIs and file-offset modes
run: |
set -eu
bin="$MEDIA_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin"
unity="$RUNNER_TEMP/android-record-unity.c"
: > "$unity"
for source in gfx/common/mediacodec_inventory.c record/record_mediacodec_encoder.c \
record/record_mux_mediacodec.c record/record_mediacodec_worker.c \
record/record_mediacodec_surface.c record/record_android_capture_images.c \
record/record_android_capture_submit.c record/record_android_capture_gl.c \
record/record_android_capture_draw.c record/record_mediacodec_gpu_worker.c \
record/record_mediacodec_file.c record/record_gpu_queue.c \
record/record_audio_queue.c libretro-common/queues/retro_spsc.c; do
printf '#include "%s/%s"\n' "$PWD" "$source" >> "$unity"
done
for target in armv7a-linux-androideabi aarch64-linux-android; do
for api in 21 29; do
for offset in default 64; do
extra=""
if [ "$offset" = 64 ]; then extra="-D_FILE_OFFSET_BITS=64"; fi
CC="$bin/$target$api-clang $extra" python3 libretro-common/samples/formats/vp9/check_stack.py
"$bin/$target$api-clang" $extra -O1 -std=c89 -pedantic -Wall -Wextra -Werror \
-Wno-long-long -Wno-c99-extensions -Wno-c11-extensions -Wframe-larger-than=2048 \
-DHAVE_THREADS -DHAVE_DYLIB -Ilibretro-common/include -Igfx/include \
-c "$unity" -o "$RUNNER_TEMP/record-$target-$api-$offset.o"
if "$bin/llvm-nm" -u "$RUNNER_TEMP/record-$target-$api-$offset.o" | \
grep -Eq ' U (AMedia|ANativeWindow|AHardwareBuffer|egl|gl[A-Z]|record_android_capture_|record_mediacodec_|record_mux_mediacodec_|record_audio_queue_|record_gpu_queue_|retro_spsc_|mediacodec_inventory_)'; then
echo "Unresolved Android recording dependency"
exit 1
fi
for source in android_image_queue android_image_ndk mediacodec_session android_frame \
mediacodec_color mediacodec_frame_export vulkan_android_device \
vulkan_android_image vulkan_android_pipeline vulkan_android_sync \
vulkan_android_submit vulkan_android_cache_entry vulkan_android_cache vulkan_android_queue; do
"$bin/$target$api-clang" $extra -O1 -std=c89 -pedantic -Wall -Wextra -Werror \
-Wno-long-long -Wno-c99-extensions -Wno-c11-extensions -Wframe-larger-than=2048 \
-DHAVE_THREADS -DHAVE_DYLIB -Ilibretro-common/include -Igfx/include \
-c "gfx/common/$source.c" -o "$RUNNER_TEMP/$source-$target-$api-$offset.o"
done
done
done
done
- name: Link media-enabled Android unity frontend
run: |
set -eu
features="-DHAVE_RAAC -DHAVE_RMP4 -DHAVE_RWEBM -DHAVE_RVP9 -DHAVE_RAV1 -DHAVE_RVORBIS -DHAVE_ROPUS"
"$MEDIA_NDK/ndk-build" -j2 \
NDK_PROJECT_PATH=. \
APP_BUILD_SCRIPT=pkg/android/phoenix-common/jni/Android.mk \
NDK_APPLICATION_MK=pkg/android/phoenix-common/jni/Application.mk \
NDK_OUT="$RUNNER_TEMP/android-media-obj" \
NDK_LIBS_OUT="$RUNNER_TEMP/android-media-libs" \
TARGET_ABIS=arm64-v8a APP_ABI=arm64-v8a \
GLES=3 NDK_GL_HEADER_VER=android-29 APP_PLATFORM=android-29 \
APP_CFLAGS="$features" APP_CPPFLAGS="$features"
test -s "$RUNNER_TEMP/android-media-libs/arm64-v8a/libretroarch-activity.so"
codec-inventory:
runs-on: ubuntu-latest
strategy:
matrix:
cc: [gcc, clang]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Real JNI codec admission and exception contracts
run: make -C samples/gfx/mediacodec_inventory clean all check CC=${{ matrix.cc }} SANITIZER=undefined
image-ownership:
runs-on: ubuntu-latest
strategy:
matrix:
cc: [gcc, clang]
steps:
- uses: actions/checkout@v6
- name: Image ownership and runtime reader fault contracts
run: make -C samples/gfx/android_image_queue clean all check CC=${{ matrix.cc }} SANITIZER=address,undefined
codec-session:
runs-on: ubuntu-latest
strategy:
matrix:
cc: [gcc, clang]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Android encoder, AAC, mux and worker contracts
run: make -C samples/record/mediacodec_encode check CC=${{ matrix.cc }} SANITIZER=address,undefined
- name: Cold session, Surface timestamps and terminal output contracts
run: make -C samples/gfx/mediacodec_session session-check CC=${{ matrix.cc }} SANITIZER=address,undefined
- name: Opaque frame leases, HDR refinement and concurrent handoffs
run: make -C samples/gfx/mediacodec_session frame-check CC=${{ matrix.cc }} SANITIZER=address,undefined
- name: HEVC VP9 AV1 color intersection and renderer export contracts
run: make -C samples/gfx/mediacodec_session export-check CC=${{ matrix.cc }} SANITIZER=address,undefined
vulkan-import-resources:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
cc: [gcc, clang]
env:
APT_PKGS: libvulkan-dev mesa-vulkan-drivers glslc spirv-tools libegl1 libgles2 libgl1-mesa-dri
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: FD ownership, warm submission and cache retirement contracts
run: make -C samples/gfx/mediacodec_session warm-check CC=${{ matrix.cc }} SANITIZER=address,undefined
- name: Modeled image resources, device admission and pipeline contracts
run: make -C samples/gfx/mediacodec_session vulkan-check CC=${{ matrix.cc }} SANITIZER=address,undefined
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install pixel oracle and shader validation dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Execute embedded and freshly compiled SDR PQ HLG cropped pixels
run: make -C samples/gfx/mediacodec_session vulkan-gpu-check CC=${{ matrix.cc }} SANITIZER=
- name: GLES recording SDR PQ and scRGB pixel oracle
run: EGL_PLATFORM=surfaceless LIBGL_ALWAYS_SOFTWARE=1 CC=${{ matrix.cc }} python3 samples/record/mediacodec_encode/draw_pixels.py
+16
View File
@@ -12,7 +12,23 @@ name: CI Apple
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Apple.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Apple.yml'
# ci_paths: end
permissions:
contents: read
+61
View File
@@ -0,0 +1,61 @@
name: Deferred auto-state regression
on:
push:
paths:
- 'runloop.c'
- 'runloop.h'
- 'runahead.c'
- 'command.c'
- 'samples/runloop/auto_state_load/**'
- '.github/workflows/Auto-state-load.yml'
pull_request:
paths:
- 'runloop.c'
- 'runloop.h'
- 'runahead.c'
- 'command.c'
- 'samples/runloop/auto_state_load/**'
- '.github/workflows/Auto-state-load.yml'
jobs:
regression:
runs-on: ubuntu-latest
env:
APT_PKGS: gcc-mingw-w64-x86-64
steps:
- uses: actions/checkout@v4
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Linux and sanitizer tests
working-directory: samples/runloop/auto_state_load
run: |
make check
make clean
make check EXTRA_CFLAGS='-DHAVE_NETWORKING -DHAVE_CHEEVOS' SANITIZER=address,undefined
- name: Windows cross-build
working-directory: samples/runloop/auto_state_load
run: |
make clean
make CC=x86_64-w64-mingw32-gcc TARGET=auto_state_load_test.exe EXTRA_CFLAGS='-DHAVE_NETWORKING -DHAVE_CHEEVOS'
+94
View File
@@ -0,0 +1,94 @@
name: CI local workflow runner
on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
ci-local-selftest:
name: tools/ci_local.py reads and runs every workflow
runs-on: ubuntu-latest
env:
APT_PKGS: python3-yaml
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Self-test
shell: bash
run: |
set -eu
# Every workflow parses exactly as PyYAML reads it, every job and
# matrix combination resolves in a dry run, and a fixture job runs
# with GitHub's step semantics.
python3 tools/ci_local.py selftest
- name: Every runner job installs through apt-install.sh
shell: bash
run: |
set -eu
# A bare apt-get waits on a stalled mirror until the job is
# cancelled, and fetches every package again each run.
python3 tools/ci_apt_check.py --selftest
python3 tools/ci_apt_check.py
- name: Every workflow's paths filter names the samples it runs
shell: bash
run: |
set -eu
# A workflow skips a push that touches only samples it does not
# build or run; a step that starts naming another sample, or a
# sample's files that start reaching into another, leave the
# filter written by tools/ci_paths.py out of date.
python3 tools/ci_paths.py --check
- name: compile_commands.py self-test
shell: bash
run: |
set -eu
python3 tools/compile_commands.py --selftest
- name: A checker workflow, run through the runner
shell: bash
run: |
set -eu
python3 tools/ci_local.py run Extern-C-Prototype-Check
+42 -38
View File
@@ -4,9 +4,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Compile-Matrix.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Compile-Matrix.yml'
# ci_paths: end
permissions:
contents: read
@@ -17,47 +29,39 @@ concurrency:
jobs:
compile-matrix: # every codec combination real builds select, not just this one
runs-on: ubuntu-latest
# Ubuntu 26.04 ships no MIPS cross toolchain; the mipsel lanes
# need 24.04's gcc-mipsel-linux-gnu.
runs-on: ubuntu-24.04
env:
APT_PKGS: build-essential libgl-dev libegl-dev libgles-dev libsdl1.2-dev gcc-mingw-w64-x86-64 clang gcc-aarch64-linux-gnu gcc-mipsel-linux-gnu libdrm-dev libgbm-dev
timeout-minutes: 30
steps:
- name: Dependencies
timeout-minutes: 20
run: |
# The gl2 arms compile against the desktop GL and EGL headers,
# and the Dingux and RS90 video drivers against SDL 1.2's.
# The Direct3D and WASAPI lanes cross-compile with mingw-w64.
pkgs="build-essential libgl-dev libegl-dev libgles-dev libsdl1.2-dev gcc-mingw-w64-x86-64 clang gcc-aarch64-linux-gnu gcc-mipsel-linux-gnu"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Compile matrix
run: ./.github/workflows/scripts/compile-matrix.sh
+35 -4
View File
@@ -100,6 +100,8 @@ jobs:
./vfs_seek_contract_test
./vfs_mapped_ptr_test
./vfs_v5_metadata_test
./vfs_readahead_test
./vfs_close_result_test
}
# Mapping on (what the platform actually ships), mapping off
# with the descriptor path forced (what the consoles have),
@@ -141,14 +143,34 @@ jobs:
steps:
- uses: actions/checkout@v5
# The system upgrade runs in the action: an msys2-runtime update
# kills every MSYS2 process, which a run step's own shell would
# not survive.
- uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.sys }}
update: true
install: >-
base-devel
pacboy: >-
toolchain:p
- name: Install packages
shell: msys2 {0}
run: |
# A mirror that stalls or rate-limits (HTTP 429) fails pacman's
# whole transaction; what it fetched stays in the cache, so a
# retry fetches only the rest, one file at a time after a
# growing wait so a shared runner's rate limit can clear.
# pacman waits out a slow mirror instead of aborting;
# timeout-minutes bounds a hang.
p=$MINGW_PACKAGE_PREFIX
pkgs="base-devel $p-toolchain"
for i in 1 2 3 4 5; do
pacman -S --noconfirm --needed --disable-download-timeout $pkgs &&
exit 0
[ $i = 5 ] && break
echo "package install failed (attempt $i); retrying in $((30 * i))s, serial downloads"
sed -i -e '/^#*ParallelDownloads/d' -e '/^\[options\]/a ParallelDownloads = 1' /etc/pacman.conf
sleep $((30 * i))
done
exit 1
- name: Build and run
working-directory: libretro-common/samples/file/vfs
@@ -175,6 +197,15 @@ jobs:
# Exercises the Win32 CreateFileMapping/MapViewOfFile
# backend, which no other job in the tree reaches.
./vfs_mapped_ptr_test.exe
# Mapped read-ahead: PrefetchVirtualMemory, the pre-Windows 8
# toucher thread (forced) and neither.
./vfs_readahead_test.exe
RETRO_VFS_PREFETCH=touch ./vfs_readahead_test.exe
RETRO_VFS_PREFETCH=none ./vfs_readahead_test.exe
# ... and never on, or stalling, the reader's thread.
./vfs_prefetch_offthread_test.exe
# The descriptor path's close() result, with PS2's semantics.
./vfs_close_result_test.exe
# The Win64 fastjmp_set stores xmm6-xmm15 with movaps and faults
# on a misaligned buffer. Linux has no such stores, so the Linux
+17 -2
View File
@@ -4,9 +4,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Ctx-Presentable-Check.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Ctx-Presentable-Check.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -26,6 +38,9 @@ jobs:
- name: Checkout
uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Checker self-test
shell: bash
run: |
@@ -33,7 +48,7 @@ jobs:
# Fixture pass: a correct driver is clean, a presentable()
# casting another driver's context type is flagged, and one
# that is defined but never wired into the vtable is flagged.
python3 tools/ctx_presentable_check.py --selftest
tools/check.sh tools/ctx_presentable_check.c --selftest
- name: Tree check
shell: bash
@@ -43,4 +58,4 @@ jobs:
# presentable() that casts the wrong driver's type, or that is
# never wired in, is invisible to every other job. This is the
# check that sees all of them at once.
python3 tools/ctx_presentable_check.py
tools/check.sh tools/ctx_presentable_check.c
+60 -2
View File
@@ -3,7 +3,23 @@ name: CI DOS/DJGPP
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/DOS-DJGPP.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/DOS-DJGPP.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -23,15 +39,57 @@ jobs:
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-djgpp-build-container:latest
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: actions/checkout@v4
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: .ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Set up ccache
run: |
# Best effort: without ccache, ccache-make.sh runs plain make.
command -v ccache > /dev/null || {
s=$(command -v sudo || true)
for i in 1 2 3; do
$s timeout 90 apt-get -yq update \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 &&
$s timeout 120 apt-get -yq install --no-install-recommends \
ccache > /dev/null && break
echo "installing ccache failed (attempt $i); retrying in 10s"
sleep 10
done
}
echo "CCACHE_DIR=$GITHUB_WORKSPACE/.ccache" >> "$GITHUB_ENV"
echo "CCACHE_MAXSIZE=250M" >> "$GITHUB_ENV"
if command -v ccache > /dev/null; then
CCACHE_DIR="$GITHUB_WORKSPACE/.ccache" ccache -z
fi
- name: Compile RA
run: |
make -f Makefile.dos -j$(getconf _NPROCESSORS_ONLN) clean
make -f Makefile.dos -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1 info all
bash .github/workflows/scripts/ccache-make.sh -f Makefile.dos -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1 info all
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv || ccache -s
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: .ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
- name: Get short SHA
id: slug
+17 -1
View File
@@ -3,7 +3,23 @@ name: CI Emscripten
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Emscripten.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Emscripten.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -23,7 +39,7 @@ jobs:
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-emscripten:latest
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: taiki-e/checkout-action@v1
+17 -2
View File
@@ -4,9 +4,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Extern-C-Prototype-Check.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Extern-C-Prototype-Check.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -26,6 +38,9 @@ jobs:
- name: Checkout
uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Checker self-test
shell: bash
run: |
@@ -34,7 +49,7 @@ jobs:
# with a parameter more than its prototype (the shape of the
# 0b2613d Xbox boot crash) is flagged, and a parameter of
# another type is flagged.
python3 tools/extern_c_prototype_check.py --selftest
tools/check.sh tools/extern_c_prototype_check.c --selftest
- name: Tree check
shell: bash
@@ -45,4 +60,4 @@ jobs:
# never checked against the prototype by any compiler: the
# linker matches on the name alone. This is the check that
# compares the two, for every such definition in the tree.
python3 tools/extern_c_prototype_check.py
tools/check.sh tools/extern_c_prototype_check.c
+19
View File
@@ -15,7 +15,23 @@ name: CI FreeBSD
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/FreeBSD.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/FreeBSD.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -35,6 +51,9 @@ jobs:
- name: Check Out Repo
uses: actions/checkout@v4
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Build in FreeBSD
uses: vmactions/freebsd-vm@v1
with:
+62 -3
View File
@@ -3,7 +3,23 @@ name: CI GameCube
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/GameCube.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/GameCube.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -22,13 +38,56 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-devkitpro:latest
options: --user root
image: devkitpro/devkitppc:latest
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: taiki-e/checkout-action@v1
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: .ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Set up ccache
run: |
# Best effort: without ccache, ccache-make.sh runs plain make.
command -v ccache > /dev/null || {
s=$(command -v sudo || true)
for i in 1 2 3; do
$s timeout 90 apt-get -yq update \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 &&
$s timeout 120 apt-get -yq install --no-install-recommends \
ccache > /dev/null && break
echo "installing ccache failed (attempt $i); retrying in 10s"
sleep 10
done
}
echo "CCACHE_DIR=$GITHUB_WORKSPACE/.ccache" >> "$GITHUB_ENV"
echo "CCACHE_MAXSIZE=250M" >> "$GITHUB_ENV"
if command -v ccache > /dev/null; then
CCACHE_DIR="$GITHUB_WORKSPACE/.ccache" ccache -z
fi
- name: Compile RA
run: |
make -f Makefile.ngc -j$(getconf _NPROCESSORS_ONLN) clean
make -f Makefile.ngc -j$(getconf _NPROCESSORS_ONLN) EXTERNAL_LIBOGC=1 GX_PTHREAD_LEGACY=0 HAVE_STATIC_DUMMY=1 info all
bash .github/workflows/scripts/ccache-make.sh -f Makefile.ngc -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1 info all
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv || ccache -s
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: .ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+75 -34
View File
@@ -4,9 +4,25 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-Headless.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-Headless.yml'
# ci_paths: end
permissions:
contents: read
@@ -21,45 +37,70 @@ concurrency:
jobs:
linux-nomenu: # Smoketest build with no menu support compiled in
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential libxkbcommon-dev libx11-xcb-dev zlib1g-dev libfreetype-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev nvidia-cg-toolkit nvidia-cg-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm ccache
timeout-minutes: 30
steps:
- name: Dependencies
timeout-minutes: 20
run: |
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev zlib1g-dev libfreetype6-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev nvidia-cg-toolkit nvidia-cg-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Enable ccache
run: |
set -eu
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 250M
ccache -z
- name: Configure
run: ./configure --disable-menu
- name: Build
run: make
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+104
View File
@@ -0,0 +1,104 @@
name: CI Linux (aarch64, OpenXR)
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-aarch64.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-aarch64.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
workflow_dispatch:
permissions:
contents: read
env:
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
build:
runs-on: ubuntu-24.04-arm
timeout-minutes: 30
env:
APT_PKGS: >-
build-essential pkg-config
libwayland-dev wayland-protocols libxkbcommon-dev libdecor-0-dev
libx11-dev libxext-dev libxrandr-dev libxinerama-dev libxxf86vm-dev
libxi-dev libx11-xcb-dev libxcb1-dev
libegl-dev libgl-dev libgles-dev libgbm-dev libdrm-dev libvulkan-dev
libasound2-dev libpulse-dev libpipewire-0.3-dev
libudev-dev libusb-1.0-0-dev libdbus-1-dev libsystemd-dev
libfreetype-dev libfontconfig-dev zlib1g-dev libsdl2-dev libv4l-dev
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Configure
run: |
./configure --disable-qt --disable-ffmpeg --disable-cg --enable-openxr
grep -q '^HAVE_OPENXR = 1' config.mk
grep -q '^HAVE_XSCRNSAVER = 1' config.mk
- name: Compile RA
run: make -j$(getconf _NPROCESSORS_ONLN)
- name: Check and strip
run: |
./retroarch --version
if readelf -d retroarch | grep -q 'libXss'; then
echo 'retroarch must load libXss at run time, not link it'
exit 1
fi
strip retroarch
- name: Get short SHA
id: slug
run: echo "sha8=$(echo ${GITHUB_SHA} | cut -c1-8)" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v4
with:
name: retroarch-linux-aarch64-${{ steps.slug.outputs.sha8 }}
path: retroarch
if-no-files-found: error
+74 -44
View File
@@ -40,9 +40,25 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-asan-ubsan.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-asan-ubsan.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -59,56 +75,56 @@ jobs:
asan-ubsan:
name: Build with ASan+UBSan and run headless smoke
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential libxkbcommon-dev libx11-xcb-dev xvfb x11-utils zlib1g-dev libfreetype-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev nvidia-cg-toolkit nvidia-cg-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm ccache
timeout-minutes: 25
steps:
- name: Install dependencies
# Mirrors Linux-Headless.yml's apt set so the build matches a
# known-good headless configuration. Adds:
# xvfb / x11-utils -- virtual X server for the imageviewer
# smoke step + xdpyinfo for diagnostics
# libsdl2-dev (already in the base set) provides the SDL2
# video driver used by that smoke -- see the smoke step's
# comment for why SDL2 over xvideo. No sanitizer-specific
# packages required; libasan / libubsan ship with the gcc
# that ubuntu-latest has installed by default.
timeout-minutes: 20
run: |
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev nvidia-cg-toolkit nvidia-cg-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Enable ccache
run: |
set -eu
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 250M
ccache -z
- name: Configure (no menu, no discord/cheevos/networking, sdl2 on)
# Trim the build surface for the first iteration so any
# sanitizer hit is a RetroArch-internal bug rather than noise
@@ -461,3 +477,17 @@ jobs:
fi
test -s /tmp/lsan-shots/exit.png
echo "[pass] screenshot on exit, no leaks outside the system libraries"
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
@@ -3,8 +3,20 @@ name: CI Linux native audio transport
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-audio-transport.yml'
# ci_paths: end
pull_request:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-audio-transport.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -24,6 +36,9 @@ jobs:
sanitizer: ['', 'address,undefined']
steps:
- uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Build the shipping frontend and real audio wrapper fixture
working-directory: samples/audio/pipeline_wakeups
run: make clean all SANITIZER='${{ matrix.sanitizer }}'
@@ -63,3 +78,51 @@ jobs:
ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: make check-auto SANITIZER='${{ matrix.sanitizer }}'
- name: A device's lend reached through the real wrapper
working-directory: samples/audio/pipeline_wakeups
env:
ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: make check-lend SANITIZER='${{ matrix.sanitizer }}'
- name: Pause boundary on the real wrapper
working-directory: samples/audio/pipeline_wakeups
env:
ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: make check-pause SANITIZER='${{ matrix.sanitizer }}'
- name: Fast-forward stall on the real wrapper
working-directory: samples/audio/pipeline_wakeups
env:
ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: make check-ff-stall SANITIZER='${{ matrix.sanitizer }}'
- name: Consumer leaving under a parked producer
working-directory: samples/audio/pipeline_wakeups
env:
ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: make check-consumer-exit SANITIZER='${{ matrix.sanitizer }}'
- name: Device kept stopped across a rebuild
working-directory: samples/audio/pipeline_wakeups
env:
ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: make check-kept-device SANITIZER='${{ matrix.sanitizer }}'
- name: Menu mixer continues without frame submissions
working-directory: samples/audio/pipeline_wakeups
env:
ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: make check-menu SANITIZER='${{ matrix.sanitizer }}'
- name: Consumer leaving and a kept device (ThreadSanitizer)
if: matrix.sanitizer == ''
working-directory: samples/audio/pipeline_wakeups
env:
TSAN_OPTIONS: halt_on_error=1 second_deadlock_stack=1
run: |
# Last: it rebuilds the fixture instrumented. The lanes are the
# pipe_consumer_gone handshake and a stopped wrapper beside the
# state rebuilt under it, which only the race detector can judge.
make clean
make check-consumer-exit check-kept-device check-menu check-ff-stall \
SANITIZER=thread
+73 -38
View File
@@ -4,9 +4,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-autoconf-tests.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-autoconf-tests.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -23,6 +35,8 @@ jobs:
autoconf-tests:
name: Build and run the autoconfig regression suite (${{ matrix.lane }})
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential zlib1g-dev libfreetype-dev ccache
timeout-minutes: ${{ matrix.sanitizer == '' && 15 || 40 }}
strategy:
fail-fast: false
@@ -39,44 +53,51 @@ jobs:
sanitizer: thread
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential zlib1g-dev libfreetype6-dev"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ strategy.job-index }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-${{ strategy.job-index }}-
- name: Enable ccache
run: |
set -eu
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 250M
ccache -z
- name: Configure (headless, test drivers)
run: |
set -eu
@@ -91,7 +112,7 @@ jobs:
- name: Build
run: |
if [ -n "${{ matrix.sanitizer }}" ]; then
make -j"$(nproc)" SANITIZER=${{ matrix.sanitizer }} DEBUG=1
CFLAGS="-O0 -g1" CXXFLAGS="-O0 -g1" make -j"$(nproc)" SANITIZER=${{ matrix.sanitizer }} DEBUG=1
else
make -j"$(nproc)"
fi
@@ -104,7 +125,21 @@ jobs:
if [ "${{ matrix.sanitizer }}" = thread ]; then
# TSan cannot map its shadow under the runner's ASLR
# entropy; setarch -R carries to every child the suite runs.
setarch "$(uname -m)" -R ./tests-other/test_autoconf.sh ./retroarch
setarch "$(uname -m)" -R ./samples/input/test_input/test_autoconf.sh ./retroarch
else
./tests-other/test_autoconf.sh ./retroarch
./samples/input/test_input/test_autoconf.sh ./retroarch
fi
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ strategy.job-index }}-${{ github.sha }}
+74
View File
@@ -0,0 +1,74 @@
name: CI CDFS sector I/O
on:
push:
branches: [master]
paths:
- 'libretro-common/formats/cdfs/**'
- 'libretro-common/include/formats/cdfs.h'
- 'libretro-common/samples/formats/cdfs/**'
- 'cheevos/cheevos.c'
- '.github/workflows/Linux-cdfs-io.yml'
pull_request:
branches: [master]
paths:
- 'libretro-common/formats/cdfs/**'
- 'libretro-common/include/formats/cdfs.h'
- 'libretro-common/samples/formats/cdfs/**'
- 'cheevos/cheevos.c'
- '.github/workflows/Linux-cdfs-io.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
cdfs:
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential gcc-mingw-w64-x86-64 wine64
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 15
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Linux sectors and faults
working-directory: libretro-common/samples/formats/cdfs
run: |
set -eu
make -j2 SANITIZER=address,undefined SAMPLE_OBJDIR=obj-ci-asan
./cdfs_dir_record_test
./cdfs_pure_api_test
./cdfs_io_error_test
env:
ASAN_OPTIONS: detect_leaks=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
- name: Windows sectors and faults
working-directory: libretro-common/samples/formats/cdfs
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
make -j2 CC=x86_64-w64-mingw32-gcc OS=Windows_NT EXE=.exe SAMPLE_OBJDIR=obj-ci-win
WINEDEBUG=-all wine ./cdfs_dir_record_test.exe
WINEDEBUG=-all wine ./cdfs_pure_api_test.exe
WINEDEBUG=-all wine ./cdfs_io_error_test.exe
+51 -7
View File
@@ -4,9 +4,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-driver-vtables.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-driver-vtables.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -23,20 +35,52 @@ jobs:
driver-vtables:
name: Every driver against its interface
runs-on: ubuntu-latest
env:
APT_PKGS: --no-install-recommends build-essential libasound2-dev libpulse-dev libjack-jackd2-dev libopenal-dev libpipewire-0.3-dev libsdl2-dev
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Check no input driver's table outgrows the structure
shell: bash
run: |
set -eu
sudo apt-get update -qq
sudo apt-get install -y -qq --no-install-recommends \
build-essential libasound2-dev libpulse-dev libjack-jackd2-dev \
libopenal-dev libpipewire-0.3-dev libsdl2-dev
- name: Checkout
uses: actions/checkout@v5
# The step below compiles the drivers Linux can. The UWP,
# Cocoa and console input drivers are not among them, and a
# table with one entry more than struct input_driver has
# members is only a warning to gcc anyway - and C2078, a
# failed build, to MSVC. That reached master for the UWP
# driver. This counts the entries of every input driver's
# table in the source, whatever SDK the driver needs.
tools/check.sh tools/input_driver_table_check.c --selftest
tools/check.sh tools/input_driver_table_check.c
- name: Compile each driver against its interface
shell: bash
+88
View File
@@ -0,0 +1,88 @@
name: CI optical DVD blocks
on:
push:
branches: [master]
paths:
- 'tasks/task_content_disc.c'
- 'libretro-common/cdrom/**'
- 'libretro-common/vfs/**'
- 'libretro-common/include/cdrom/**'
- 'libretro-common/include/vfs/**'
- 'libretro-common/samples/file/vfs_cdrom/**'
- '.github/workflows/Linux-dvd-blocks.yml'
pull_request:
branches: [master]
paths:
- 'tasks/task_content_disc.c'
- 'libretro-common/cdrom/**'
- 'libretro-common/vfs/**'
- 'libretro-common/include/cdrom/**'
- 'libretro-common/include/vfs/**'
- 'libretro-common/samples/file/vfs_cdrom/**'
- '.github/workflows/Linux-dvd-blocks.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
blocks:
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential gcc-mingw-w64-x86-64 wine64
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 15
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Linux commands and VFS
working-directory: libretro-common/samples/file/vfs_cdrom
run: |
set -eu
make -j2 dvd_blocks_test SANITIZER=address,undefined SAMPLE_OBJDIR=obj-dvd-asan
./dvd_blocks_test
make -j2 disc_dump_test SANITIZER=address,undefined SAMPLE_OBJDIR=obj-dvd-asan
./disc_dump_test
make -j2 vfs_close_test SANITIZER=address,undefined SAMPLE_OBJDIR=obj-dvd-asan
./vfs_close_test
make -j2 macos_command_test SANITIZER=address,undefined SAMPLE_OBJDIR=obj-dvd-asan
./macos_command_test
make -j2 SANITIZER=address,undefined SAMPLE_OBJDIR=obj-dvd-asan
./cdrom_cuesheet_overflow_test
env:
ASAN_OPTIONS: detect_leaks=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
- name: Windows commands and VFS
working-directory: libretro-common/samples/file/vfs_cdrom
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
make -j2 dvd_blocks_test.exe CC=x86_64-w64-mingw32-gcc OS=Windows_NT EXE=.exe SAMPLE_OBJDIR=obj-dvd-win
WINEDEBUG=-all wine ./dvd_blocks_test.exe
make -j2 disc_dump_test.exe CC=x86_64-w64-mingw32-gcc OS=Windows_NT EXE=.exe SAMPLE_OBJDIR=obj-dvd-win
WINEDEBUG=-all wine ./disc_dump_test.exe
make -j2 vfs_close_test.exe CC=x86_64-w64-mingw32-gcc OS=Windows_NT EXE=.exe SAMPLE_OBJDIR=obj-dvd-win
WINEDEBUG=-all wine ./vfs_close_test.exe
make -j2 macos_command_test.exe CC=x86_64-w64-mingw32-gcc OS=Windows_NT EXE=.exe SAMPLE_OBJDIR=obj-dvd-win
WINEDEBUG=-all wine ./macos_command_test.exe
+135
View File
@@ -0,0 +1,135 @@
name: CI Linux EGL hardware core
# Runs the gl video driver on the X11 EGL context under Xvfb with an
# OpenGL hardware core, and checks that the core was given a context
# and that its frame is what reaches the screen - with threaded video
# on, where the core's context lives on the main thread and cannot be
# bound to the window surface the video thread holds, and with it off.
# Mesa takes the surfaceless path by itself; a preloaded shim hides
# parts of its EGL so the pbuffer paths run too.
on:
push:
branches:
- master
paths:
- 'gfx/common/egl_common.c'
- 'gfx/common/egl_common.h'
- 'gfx/drivers_context/xegl_ctx.c'
- 'gfx/drivers/gl2.c'
- 'gfx/video_driver.c'
- 'gfx/video_thread_hw.c'
- 'gfx/video_thread_wrapper.c'
- 'samples/gfx/egl_hw_core/**'
- '.github/workflows/Linux-egl-hw-core.yml'
pull_request:
paths:
- 'gfx/common/egl_common.c'
- 'gfx/common/egl_common.h'
- 'gfx/drivers_context/xegl_ctx.c'
- 'gfx/drivers/gl2.c'
- 'gfx/video_driver.c'
- 'gfx/video_thread_hw.c'
- 'gfx/video_thread_wrapper.c'
- 'samples/gfx/egl_hw_core/**'
- '.github/workflows/Linux-egl-hw-core.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
egl-hw-core:
name: Run a GL hardware core on the EGL context under Xvfb
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential zlib1g-dev ccache python3 xvfb libgl1-mesa-dri libx11-dev libx11-xcb-dev libxcb1-dev libxext-dev libxxf86vm-dev libxrandr-dev libxinerama-dev libgl-dev libegl-dev
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Enable ccache
run: |
set -eu
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 250M
ccache -z
- name: Build RetroArch (X11 + OpenGL + EGL, the rest off)
shell: bash
run: |
set -eu
./configure --disable-qt --disable-wayland --disable-sdl \
--disable-sdl2 --disable-ffmpeg --disable-vulkan \
--disable-pulse --disable-discord \
--disable-networking --disable-cheevos \
--disable-glslang --disable-slang \
--disable-spirv_cross --disable-xmb \
--disable-materialui --disable-ozone \
--disable-7zip --disable-flac \
--disable-audiomixer --disable-translate \
--disable-accessibility --disable-crtswitchres \
--disable-langextra --disable-libretrodb \
--disable-video_filter --disable-dsp_filter
grep -q '^HAVE_OPENGL = 1' config.mk
grep -q '^HAVE_EGL = 1' config.mk
grep -q '^HAVE_X11 = 1' config.mk
make -j$(nproc)
ccache -s
- name: Run egl_hw_core_test
shell: bash
run: |
set -eu
make -C samples/gfx/egl_hw_core
timeout 600 python3 samples/gfx/egl_hw_core/egl_hw_core_test.py ./retroarch
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+28 -1
View File
@@ -9,7 +9,12 @@ name: CI Linux filter plugins
# takes more than one worker must give the single-worker output byte for
# byte with 2, 3 and 4 workers running at once. The same run is repeated
# with the plugins and the harness built under ASan, UBSan and
# LeakSanitizer, and under ThreadSanitizer.
# LeakSanitizer, and under ThreadSanitizer. The NTSC, 2xBR, NTSC-CRT,
# 2xSaI-family, Phosphor2x, Blargg NTSC SNES, De-Dither, Pixel Art AA,
# Scale2x, Scanline2x, Upscale 1.5x/1.66x and the Normal, Grid, Game Boy
# and Dot Matrix scalers are also held byte for byte to output hashes
# recorded from their earlier implementations
# (samples/gfx/{ntsc,crt}_filter_golden and samples/gfx/filter_golden).
on:
push:
@@ -20,6 +25,9 @@ on:
- 'libretro-common/audio/dsp_filters/**'
- 'libretro-common/include/libretro_dspfilter.h'
- 'samples/gfx/filter_plugins/**'
- 'samples/gfx/ntsc_filter_golden/**'
- 'samples/gfx/crt_filter_golden/**'
- 'samples/gfx/filter_golden/**'
- '.github/workflows/Linux-filter-plugins.yml'
pull_request:
paths:
@@ -27,6 +35,9 @@ on:
- 'libretro-common/audio/dsp_filters/**'
- 'libretro-common/include/libretro_dspfilter.h'
- 'samples/gfx/filter_plugins/**'
- 'samples/gfx/ntsc_filter_golden/**'
- 'samples/gfx/crt_filter_golden/**'
- 'samples/gfx/filter_golden/**'
- '.github/workflows/Linux-filter-plugins.yml'
workflow_dispatch:
@@ -47,6 +58,9 @@ jobs:
- name: Checkout
uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Build the plugins and the harness
run: |
set -eu
@@ -70,6 +84,19 @@ jobs:
make -C samples/gfx/filter_plugins clean
make -C samples/gfx/filter_plugins check SANITIZER=address,undefined
- name: NTSC, 2xBR, NTSC-CRT and 2xSaI-family filter output against their recorded hashes
env:
ASAN_OPTIONS: abort_on_error=1:detect_leaks=1:print_stacktrace=1
UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1
run: |
set -eu
jobs=$(getconf _NPROCESSORS_ONLN)
for t in ntsc_filter_golden crt_filter_golden filter_golden; do
make -C samples/gfx/$t -j"$jobs" check
make -C samples/gfx/$t clean
make -C samples/gfx/$t -j"$jobs" check SANITIZER=address,undefined
done
- name: Load and run every plugin under ThreadSanitizer
env:
TSAN_OPTIONS: halt_on_error=1
+143 -35
View File
@@ -19,9 +19,125 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- 'libretro-common/samples/encodings/rbzip2/**'
- 'samples/runloop/auto_state_load/**'
- 'samples/runloop/autosave_snapshot/**'
- 'samples/runloop/cloudsync_unload/**'
- 'samples/runloop/content_closing/**'
- 'samples/runloop/core_status_defer/**'
- 'samples/runloop/frontend_overhead/**'
- 'samples/runloop/hw_context_request/**'
- 'samples/runloop/log_ring/**'
- 'samples/runloop/pacing/**'
- 'samples/tasks/autodetect_handoff/**'
- 'samples/tasks/bluetooth_scan/**'
- 'samples/tasks/bps_patch/**'
- 'samples/tasks/bsv_checkpoint/**'
- 'samples/tasks/bsv_replay/**'
- 'samples/tasks/bsv_replay_init/**'
- 'samples/tasks/bsv_statestream/**'
- 'samples/tasks/cloudsync/**'
- 'samples/tasks/content_load/**'
- 'samples/tasks/content_prefetch/**'
- 'samples/tasks/core_backup/**'
- 'samples/tasks/core_updater/**'
- 'samples/tasks/database/**'
- 'samples/tasks/decompress/**'
- 'samples/tasks/exit_drain/**'
- 'samples/tasks/file_transfer/**'
- 'samples/tasks/http/**'
- 'samples/tasks/http_emscripten/**'
- 'samples/tasks/image_decode_slice/**'
- 'samples/tasks/input_remap/**'
- 'samples/tasks/natt_desc/**'
- 'samples/tasks/netplay_nat/**'
- 'samples/tasks/ordering/**'
- 'samples/tasks/overlay/**'
- 'samples/tasks/patch_stream/**'
- 'samples/tasks/pl_thumbnail/**'
- 'samples/tasks/playlist_manager/**'
- 'samples/tasks/progress_push/**'
- 'samples/tasks/save_state/**'
- 'samples/tasks/steam_dlc/**'
- 'samples/tasks/subtitle_load/**'
- 'samples/tasks/translation_reply/**'
- 'samples/tasks/turbo_bind/**'
- 'samples/tasks/video_shader_parse/**'
- 'samples/tasks/wait_timeout/**'
- 'samples/tasks/watchdog/**'
- 'samples/tasks/wifi_tasks/**'
- 'samples/runloop/**'
- 'samples/tasks/**'
- 'samples/ui_companion_log_stub.c'
- '.github/workflows/Linux-frontend-overhead.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- 'libretro-common/samples/encodings/rbzip2/**'
- 'samples/runloop/auto_state_load/**'
- 'samples/runloop/autosave_snapshot/**'
- 'samples/runloop/cloudsync_unload/**'
- 'samples/runloop/content_closing/**'
- 'samples/runloop/core_status_defer/**'
- 'samples/runloop/frontend_overhead/**'
- 'samples/runloop/hw_context_request/**'
- 'samples/runloop/log_ring/**'
- 'samples/runloop/pacing/**'
- 'samples/tasks/autodetect_handoff/**'
- 'samples/tasks/bluetooth_scan/**'
- 'samples/tasks/bps_patch/**'
- 'samples/tasks/bsv_checkpoint/**'
- 'samples/tasks/bsv_replay/**'
- 'samples/tasks/bsv_replay_init/**'
- 'samples/tasks/bsv_statestream/**'
- 'samples/tasks/cloudsync/**'
- 'samples/tasks/content_load/**'
- 'samples/tasks/content_prefetch/**'
- 'samples/tasks/core_backup/**'
- 'samples/tasks/core_updater/**'
- 'samples/tasks/database/**'
- 'samples/tasks/decompress/**'
- 'samples/tasks/exit_drain/**'
- 'samples/tasks/file_transfer/**'
- 'samples/tasks/http/**'
- 'samples/tasks/http_emscripten/**'
- 'samples/tasks/image_decode_slice/**'
- 'samples/tasks/input_remap/**'
- 'samples/tasks/natt_desc/**'
- 'samples/tasks/netplay_nat/**'
- 'samples/tasks/ordering/**'
- 'samples/tasks/overlay/**'
- 'samples/tasks/patch_stream/**'
- 'samples/tasks/pl_thumbnail/**'
- 'samples/tasks/playlist_manager/**'
- 'samples/tasks/progress_push/**'
- 'samples/tasks/save_state/**'
- 'samples/tasks/steam_dlc/**'
- 'samples/tasks/subtitle_load/**'
- 'samples/tasks/translation_reply/**'
- 'samples/tasks/turbo_bind/**'
- 'samples/tasks/video_shader_parse/**'
- 'samples/tasks/wait_timeout/**'
- 'samples/tasks/watchdog/**'
- 'samples/tasks/wifi_tasks/**'
- 'samples/runloop/**'
- 'samples/tasks/**'
- 'samples/ui_companion_log_stub.c'
- '.github/workflows/Linux-frontend-overhead.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -35,47 +151,39 @@ jobs:
linux-frontend-overhead:
name: Release build, frame-path heap and cost
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential libxkbcommon-dev libx11-xcb-dev zlib1g-dev libfreetype-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libavcodec-dev libsdl2-dev libxml2-dev yasm
timeout-minutes: 30
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev zlib1g-dev libfreetype6-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libavcodec-dev libsdl2-dev libxml2-dev yasm"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Configure
# Non-Qt, as the harness requires (with Qt, main() lives in
# ui_qt.o). Everything else at its default.
+38 -40
View File
@@ -36,52 +36,41 @@ jobs:
gl1-upload:
name: Run retroarch on gl1 under Xvfb and check the frame
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential zlib1g-dev ccache python3 xvfb libgl1-mesa-dri libx11-dev libx11-xcb-dev libxcb1-dev libxext-dev libxxf86vm-dev libxrandr-dev libxinerama-dev libgl-dev libegl-dev
timeout-minutes: 25
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
build-essential zlib1g-dev ccache python3 \
xvfb libgl1-mesa-dri \
libx11-dev libx11-xcb-dev libxcb1-dev libxext-dev \
libxxf86vm-dev libxrandr-dev libxinerama-dev \
libgl-dev libegl-dev && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
build-essential zlib1g-dev ccache python3 \
xvfb libgl1-mesa-dri \
libx11-dev libx11-xcb-dev libxcb1-dev libxext-dev \
libxxf86vm-dev libxrandr-dev libxinerama-dev \
libgl-dev libegl-dev
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache@v4
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
@@ -94,7 +83,7 @@ jobs:
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 500M
ccache -M 250M
ccache -z
- name: Build RetroArch (X11 + OpenGL, the rest off)
@@ -123,3 +112,12 @@ jobs:
set -eu
make -C samples/gfx/gl1_upload
timeout 600 python3 samples/gfx/gl1_upload/gl1_upload_test.py ./retroarch
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+104
View File
@@ -0,0 +1,104 @@
name: CI Linux Griffin [Unity build]
on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-griffin.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-griffin.yml'
# ci_paths: end
permissions:
contents: read
env:
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
linux-griffin: # griffin/griffin.c as Makefile.griffin builds it for Linux
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential libgl-dev libudev-dev libpulse-dev libx11-dev libx11-xcb-dev libxcb1-dev libxxf86vm-dev ccache
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Enable ccache
run: |
set -eu
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 250M
ccache -z
- name: Build
run: make -f Makefile.griffin platform=unix -j"$(nproc)"
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
@@ -13,9 +13,31 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- 'samples/network/cheevos_login/**'
- 'samples/network/smb_retro/**'
- 'samples/network/tls_retro/**'
- '.github/workflows/Linux-library-net-backends.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- 'samples/network/cheevos_login/**'
- 'samples/network/smb_retro/**'
- 'samples/network/tls_retro/**'
- '.github/workflows/Linux-library-net-backends.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -29,6 +51,8 @@ jobs:
library-backends:
name: ${{ matrix.lane }}
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential pkg-config cmake libgl1-mesa-dev libx11-dev libxext-dev libasound2-dev libsdl2-dev zlib1g-dev libfreetype-dev python3 openssl libmbedtls-dev libbearssl-dev samba ccache
timeout-minutes: 60
strategy:
fail-fast: false
@@ -48,17 +72,51 @@ jobs:
absent: ' T rsmb_connect'
steps:
- name: Dependencies
run: |
sudo apt-get update -y
sudo apt-get install -y build-essential pkg-config cmake \
libgl1-mesa-dev libx11-dev libxext-dev libasound2-dev libsdl2-dev \
zlib1g-dev libfreetype6-dev python3 openssl \
libmbedtls-dev libbearssl-dev samba
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ strategy.job-index }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-${{ strategy.job-index }}-
- name: Enable ccache
run: |
set -eu
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 250M
ccache -z
- name: libsmb2 from upstream (no Ubuntu package)
if: matrix.lane == 'libsmb2'
run: |
@@ -101,11 +159,18 @@ jobs:
run: |
set -eu
case "${{ matrix.lane }}" in mbedtls) be=mbed ;; bearssl) be=bear ;; esac
make clean all SSL_BACKEND=$be SANITIZER=address,undefined
make clean SSL_BACKEND=$be SANITIZER=address,undefined && make -j$(nproc) all SSL_BACKEND=$be SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
make check SSL_BACKEND=$be
echo "[pass] ${{ matrix.lane }}: cheevos login transport over a local HTTPS server"
# A server that goes silent mid-handshake: the read bound ends it.
make -C ../tls_retro clean SSL_BACKEND=$be && make -C ../tls_retro -j$(nproc) tls_stall SSL_BACKEND=$be
../tls_retro/tls_stall
# A refused certificate reaches the verify hook.
make -C ../tls_retro clean
SSL_BACKEND=$be ../tls_retro/verify_hook_test.sh
make -C ../tls_retro clean
- name: Operate the libsmb2 backend (SMB VFS against a local smbd)
if: matrix.lane == 'libsmb2'
@@ -126,7 +191,7 @@ jobs:
set -eu
cmake -S "$RUNNER_TEMP/libsmb2" -B "$RUNNER_TEMP/libsmb2/build-tsan" \
-DCMAKE_INSTALL_PREFIX=/opt/libsmb2-tsan -DBUILD_SHARED_LIBS=ON \
-DCMAKE_C_FLAGS="-O1 -g -fsanitize=thread" \
-DCMAKE_C_FLAGS="-O1 -g1 -fsanitize=thread" \
-DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=thread" >/dev/null
make -C "$RUNNER_TEMP/libsmb2/build-tsan" -j"$(nproc)" >/dev/null
sudo make -C "$RUNNER_TEMP/libsmb2/build-tsan" install >/dev/null
@@ -134,6 +199,20 @@ jobs:
PKG_CONFIG_PATH=/opt/libsmb2-tsan/lib/pkgconfig \
LD_LIBRARY_PATH=/opt/libsmb2-tsan/lib \
TSAN_OPTIONS="halt_on_error=1 exitcode=66" \
CFLAGS="-O1 -g -fsanitize=thread" LDLIBS="-fsanitize=thread" \
CFLAGS="-O1 -g1 -fsanitize=thread" LDLIBS="-fsanitize=thread" \
WITH_LIBSMB2=1 samples/network/smb_retro/local_samba_test.sh
sudo make -C samples/network/smb_retro clean
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ strategy.job-index }}-${{ github.sha }}
File diff suppressed because it is too large. Load diff
+289 -146
View File
@@ -10,9 +10,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-libretro-common-tests.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-libretro-common-tests.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -28,63 +40,46 @@ concurrency:
jobs:
libretro-common-tests:
name: Build and run libretro-common/Makefile.test
runs-on: ubuntu-latest
timeout-minutes: 15
# Ubuntu 26.04 ships no MIPS cross toolchain; the mipsel lanes
# need 24.04's gcc-mipsel-linux-gnu.
runs-on: ubuntu-24.04
env:
APT_PKGS: build-essential check pkg-config zlib1g-dev gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 wine wine64 ffmpeg gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf gcc-mipsel-linux-gnu qemu-user ccache
# The suites and the stack check have grown with the tree; the
# companion harnesses are a job of their own, run beside this one.
timeout-minutes: 30
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
# ffmpeg: tools/companion_thumbs_test.sh builds its video-hover
# fixture (a 2 KB Baseline MP4) with it.
pkgs="build-essential check pkg-config zlib1g-dev lcov gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 wine wine64 ffmpeg gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf gcc-mipsel-linux-gnu qemu-user"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
# mingw and wine are for the Windows A/W configuration matrix
# in Makefile.test. libretro-common selects between the ANSI
# and the wide Win32 entry points three ways and, until these
# were installed, no job built two of them: the ANSI branch
# was compiled by nothing, so nothing noticed when it stopped
# compiling. wine takes it past a build check - an ANSI build
# under wine really does call CreateFileA and
# GetFileAttributesA, so the branch is executed rather than
# merely parsed. Both are optional to the recipe: without
# them it says which tool is missing and passes, so a
# contributor's local run is not blocked.
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache@v4
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
@@ -94,11 +89,10 @@ jobs:
- name: Enable ccache
run: |
set -eu
command -v ccache >/dev/null 2>&1 || sudo apt-get install -y -qq ccache
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 500M
ccache -M 250M
ccache -z
- name: Build and run the unit test suites
@@ -111,8 +105,8 @@ jobs:
WINEPREFIX: "/tmp/wineprefix"
run: |
set -eu
# The recipe builds each suite, runs it, and collects
# coverage, in order -- coverage files collide otherwise.
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
# The recipe builds each suite and runs it, in order.
# Any suite failing to build, or any check failing, or any
# sanitizer report, fails the job.
# 'unit-tests' by name: the default goal of Makefile.test is
@@ -120,7 +114,9 @@ jobs:
# matrix and none of the suites this step exists to run. The
# 'all' target adds the concurrency and I/O samples, whose
# runtime depends on how many processors the runner has.
make -f Makefile.test unit-tests \
# COVERAGE=0: nothing here reads the coverage, and collecting
# it instruments every compile and runs lcov once a suite
make -f Makefile.test unit-tests COVERAGE=0 \
LIBCHECK_CFLAGS="$(pkg-config --cflags check)" \
CFLAGS="$(pkg-config --cflags check)"
echo "[pass] libretro-common unit test suites"
@@ -133,6 +129,10 @@ jobs:
# every lazily set up table or CPU check, first used by
# eight threads at once under TSan
make -f Makefile.test crypto-tsan-check
# the Windows wait tiers - eventcount and condition variable,
# high resolution timer and kernel timeout - as Windows
# binaries under wine
make -f Makefile.test win32-waits-check
- name: Check VFS backend symbol parity
shell: bash
@@ -147,7 +147,7 @@ jobs:
# That is how retro_vfs_file_get_mapped_ptr_impl reached the
# branch broken. This is the same check at source level, for
# free, on Linux.
python3 tools/vfs_backend_parity.py
tools/check.sh tools/vfs_backend_parity.c
- name: Check every command describes itself
shell: bash
@@ -157,7 +157,70 @@ jobs:
# HELP prints command.h's tables, and interfaces that hand the
# commands to a client are generated from them: a command
# added without a description reaches all of those blank.
python3 tools/command_table_check.py
tools/check.sh tools/command_table_check.c
- name: Check video and input code stay out of each other's state
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# The frontend starts the input driver; a video driver says
# only what kind of window it put up. What is still to be
# moved is listed in the check, file by file, and the lists
# only shrink: a video file that starts naming an input
# driver, or either side taking the other's state, fails.
tools/check.sh tools/input_video_separation_check.c --selftest
tools/check.sh tools/input_video_separation_check.c
- name: Check input drivers ask by name and do not take the frontend's state
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# A driver that needs something from the settings, the run
# loop, the menu or the input frontend asks for it by name;
# it does not take the whole state and pick a field out.
# What is still to be converted is listed in the check, file
# by file with a count, and the counts only go down.
tools/check.sh tools/input_state_grab_check.c --selftest
tools/check.sh tools/input_state_grab_check.c
- name: Check the GL drivers take the hardware ring's fence ahead of the swap
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# Taken after the swap, the fence completes only when a driver
# that queues swaps releases it, and a hardware rendered core
# fast-forwards at the display's rate.
tools/check.sh tools/gl_ring_fence_check.c --selftest
tools/check.sh tools/gl_ring_fence_check.c
- name: Check the GL drivers set their vsync report with the swap interval
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# Set apart, the threaded presenter counts a vsync wait as
# drawing time, or subtracts one that was not made.
tools/check.sh tools/gl_present_vsynced_check.c --selftest
tools/check.sh tools/gl_present_vsynced_check.c
- name: Check a setting's label exists wherever its translations name it
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# The translation files name a label for every string they
# have, in every build. intl/json2h.py copies a row's feature
# guard into them, unless the guard line mentions a
# SETTINGS_DEF pass: that line it drops. A line that mentions
# a pass and still depends on a feature leaves the
# translations naming a label the build does not have, and
# nothing fails until the first translation arrives from
# Crowdin - then every build without the feature does.
tools/check.sh tools/settings_def_guard_check.c --selftest
tools/check.sh tools/settings_def_guard_check.c
- name: Check no video driver deletes overlay textures it was lent
shell: bash
@@ -171,7 +234,7 @@ jobs:
# restarted. No runner can run a GL driver, so this is the
# check at source level: every driver with load_textures
# sets, tests and clears its borrowed marker.
python3 tools/overlay_texture_ownership.py
tools/check.sh tools/overlay_texture_ownership.c
- name: Check overlay setters refuse an image the driver does not hold
shell: bash
@@ -185,93 +248,7 @@ jobs:
# set_alpha; D3D, Vulkan and Metal had no check at all. No
# runner can run these drivers, so this is the check at
# source level for the ones that have been audited.
python3 tools/overlay_setter_bounds.py
- name: Check companion backend feature parity
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# The Qt, Win32 and Cocoa desktop companions share one C core
# but each has its own presentation; a backend can quietly omit
# a feature (no file browser, no scan, no core picker) and still
# build and run, the gap only showing when a user switches
# drivers. Source-level check, for free, on Linux.
python3 tools/companion_parity.py
- name: Companion thumbnail engine regression test (plain, TSan, ASan)
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# The shared thumbnail engine every desktop companion draws
# through: real decoder, decode threads, cache, priority,
# cancel, shutdown, and one hover on a video (the still and
# the animation share one session). Under both sanitizers,
# since it is the one piece of threaded code in the
# companions.
tools/companion_thumbs_test.sh
tools/companion_thumbs_test.sh tsan
tools/companion_thumbs_test.sh asan
- name: Companion core regression test (plain, ASan)
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# The toolkit-free model every desktop companion drives:
# playlist listing and All Playlists, the thumbnail path rule,
# the file browser, the run / needs-core paths, launch
# options - against real .lpl fixtures, with RetroArch's
# state replaced by stubs.
tools/companion_core_test.sh
tools/companion_core_test.sh asan
tools/companion_core_test.sh tsan
- name: C89 / Win32 syntax gate on the companion sources
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# MSVC 2005 builds these as C89 through griffin. The gate's
# second pass needs a 32-bit mingw for the Windows-only unit
# (the host gcc has no <windows.h>, which used to make the
# pass skip it silently - that is how mixed declarations
# reached master).
sudo apt-get install -y -qq gcc-mingw-w64-i686 gcc-mingw-w64-x86-64 > /dev/null
tools/mingw_syntax_check.sh ui/drivers/ui_win32_companion.c \
ui/companion/companion_core.c ui/companion/companion_thumbs.c \
ui/companion/companion_dock.c \
gfx/gfx_anim_preview.c
- name: Cocoa companion regression harness (GNUstep AppKit, headless)
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# The real ui/drivers/ui_cocoa_companion.m, compiled and linked
# against GNUstep's Foundation + AppKit with the real companion
# core and the core test's fixtures, driven under Xvfb the way a
# user drives it (view popup, File Browser tab, close) and
# checked on the AppKit objects themselves.
sudo apt-get install -y -qq gobjc gnustep-devel gnustep-back-common xvfb > /dev/null
tools/companion_cocoa_test.sh
# ASan too: the grid's prefetch reaching past the thumbnail
# marks only shows under a checking allocator (it crashed the
# app on macOS, where Apple's is one).
tools/companion_cocoa_test.sh asan
- name: Win32 companion regression harness (mingw + Wine, headless)
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# The real ui/drivers/ui_win32_companion.c, built with mingw and
# linked with the real companion core and the core test's
# fixtures, run under Wine on Xvfb and driven with the messages
# the widgets send; checked on the real controls.
sudo apt-get install -y -qq gcc-mingw-w64-x86-64 wine xvfb > /dev/null
tools/companion_win32_test.sh
tools/check.sh tools/overlay_setter_bounds.c
- name: WDM-KS ABI against the system ks.h
shell: bash
@@ -312,3 +289,169 @@ jobs:
# buffer as a local - twice that stack - on a path reached
# from a task handler, and nothing noticed.
python3 tools/stack_budget.py
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
companion-tests:
name: Companion regression tests
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential pkg-config zlib1g-dev ffmpeg ccache gcc-mingw-w64-i686 gcc-mingw-w64-x86-64 gobjc gnustep-devel gnustep-back-common xvfb wine
# beside the suites, not after them: together they took twice as
# long as either
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Enable ccache
run: |
set -eu
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 250M
ccache -z
- name: Check companion backend feature parity
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# The Qt, Win32 and Cocoa desktop companions share one C core
# but each has its own presentation; a backend can quietly omit
# a feature (no file browser, no scan, no core picker) and still
# build and run, the gap only showing when a user switches
# drivers. Source-level check, for free, on Linux.
tools/check.sh tools/companion_parity.c
- name: Companion thumbnail engine regression test (plain, TSan, ASan)
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# The shared thumbnail engine every desktop companion draws
# through: real decoder, decode threads, cache, priority,
# cancel, shutdown, and one hover on a video (the still and
# the animation share one session). Under both sanitizers,
# since it is the one piece of threaded code in the
# companions.
tools/companion_thumbs_test.sh
tools/companion_thumbs_test.sh tsan
tools/companion_thumbs_test.sh asan
- name: Companion core regression test (plain, ASan)
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# The toolkit-free model every desktop companion drives:
# playlist listing and All Playlists, the thumbnail path rule,
# the file browser, the run / needs-core paths, launch
# options - against real .lpl fixtures, with RetroArch's
# state replaced by stubs.
tools/companion_core_test.sh
tools/companion_core_test.sh asan
tools/companion_core_test.sh tsan
- name: C89 / Win32 syntax gate on the companion sources
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# MSVC 2005 builds these as C89 through griffin. The gate's
# second pass needs a 32-bit mingw for the Windows-only unit
# (the host gcc has no <windows.h>, which used to make the
# pass skip it silently - that is how mixed declarations
# reached master).
tools/mingw_syntax_check.sh ui/drivers/ui_win32_companion.c \
ui/companion/companion_core.c ui/companion/companion_thumbs.c \
ui/companion/companion_dock.c \
gfx/gfx_anim_preview.c
- name: Cocoa companion regression harness (GNUstep AppKit, headless)
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# The real ui/drivers/ui_cocoa_companion.m, compiled and linked
# against GNUstep's Foundation + AppKit with the real companion
# core and the core test's fixtures, driven under Xvfb the way a
# user drives it (view popup, File Browser tab, close) and
# checked on the AppKit objects themselves.
tools/companion_cocoa_test.sh
# ASan too: the grid's prefetch reaching past the thumbnail
# marks only shows under a checking allocator (it crashed the
# app on macOS, where Apple's is one).
tools/companion_cocoa_test.sh asan
- name: Win32 companion regression harness (mingw + Wine, headless)
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# The real ui/drivers/ui_win32_companion.c, built with mingw and
# linked with the real companion core and the core test's
# fixtures, run under Wine on Xvfb and driven with the messages
# the widgets send; checked on the real controls.
tools/companion_win32_test.sh
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+56 -38
View File
@@ -4,9 +4,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-libretro-db-samples.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-libretro-db-samples.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -23,49 +35,41 @@ jobs:
samples:
name: Build and run libretro-db/samples
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential zlib1g-dev ccache
timeout-minutes: 15
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential zlib1g-dev"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache@v4
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
@@ -75,11 +79,10 @@ jobs:
- name: Enable ccache
run: |
set -eu
command -v ccache >/dev/null 2>&1 || sudo apt-get install -y -qq ccache
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 500M
ccache -M 250M
ccache -z
- name: Build and run samples
@@ -102,6 +105,7 @@ jobs:
# failure. These are built AND executed.
declare -a RUN_TARGETS=(
rmsgpack_overflow_test
rmsgpack_window_test
libretrodb_leak_test
)
@@ -210,3 +214,17 @@ jobs:
if [[ $fails -gt 0 ]]; then
exit 1
fi
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+94
View File
@@ -0,0 +1,94 @@
name: CI Linux MCP over stdio
on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-mcp-stdio.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-mcp-stdio.yml'
# ci_paths: end
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
mcp-stdio:
name: retroarch --mcp-stdio driving a core
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential pkg-config zlib1g-dev libfreetype-dev python3 ccache
timeout-minutes: 40
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Build RetroArch and the views test core
shell: bash
run: |
set -eu
./configure --disable-qt --disable-ffmpeg --disable-vulkan \
--disable-wayland --disable-x11 --disable-opengl
grep -q '^HAVE_MCP = 1' config.mk
make -j"$(nproc)" CC="ccache gcc" CXX="ccache g++"
make -C samples/cores/video_views
# a core that maps memory, for the tools that read it
gcc -O1 -shared -fPIC -Ilibretro-common/include \
-o samples/network/command_frames/harness_core.so \
samples/network/command_frames/harness_core.c
# and one that maps none, handing its RAM over as snes9x does
gcc -O1 -shared -fPIC -DHARNESS_NO_MAP -Ilibretro-common/include \
-o samples/network/command_frames/harness_core_nomap.so \
samples/network/command_frames/harness_core.c
- name: MCP over stdin and stdout, end to end
shell: bash
run: |
set -eu
# null drivers: no display on the runner
timeout 300 python3 samples/network/mcp_server/stdio_e2e.py \
./retroarch samples/cores/video_views/video_views_libretro.so \
samples/network/command_frames/harness_core.so \
samples/network/command_frames/harness_core_nomap.so
+207 -38
View File
@@ -22,12 +22,20 @@ on:
- 'playlist.h'
- 'tasks/task_content*'
- 'samples/menu/playlist_nav/**'
- 'samples/network/command_frames/**'
- 'samples/menu/nav_repeat_stall/**'
- 'samples/menu/mouse_click/**'
- 'samples/menu/menu_icon_ownership/**'
- 'samples/menu/ozone_sublabel_scroll/**'
- 'gfx/gfx_display.c'
- 'runloop.c'
- 'samples/menu/network_settings/**'
- 'intl/msg_hash_lbl.h'
- 'msg_hash_lbl_str.h'
- 'samples/menu/resolution_reset/**'
- 'samples/menu/setting_command/**'
- 'samples/menu/core_info_page/**'
- 'samples/menu/load_content_filter/**'
- 'samples/settings/packed_rows/**'
- 'setting_list.h'
- 'gfx/video_defines.h'
@@ -39,12 +47,20 @@ on:
- 'playlist.h'
- 'tasks/task_content*'
- 'samples/menu/playlist_nav/**'
- 'samples/network/command_frames/**'
- 'samples/menu/nav_repeat_stall/**'
- 'samples/menu/mouse_click/**'
- 'samples/menu/menu_icon_ownership/**'
- 'samples/menu/ozone_sublabel_scroll/**'
- 'gfx/gfx_display.c'
- 'runloop.c'
- 'samples/menu/network_settings/**'
- 'intl/msg_hash_lbl.h'
- 'msg_hash_lbl_str.h'
- 'samples/menu/resolution_reset/**'
- 'samples/menu/setting_command/**'
- 'samples/menu/core_info_page/**'
- 'samples/menu/load_content_filter/**'
- 'samples/settings/packed_rows/**'
- 'setting_list.h'
- 'gfx/video_defines.h'
@@ -59,49 +75,41 @@ jobs:
menu-harness:
name: Build and run the menu navigation harness
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential zlib1g-dev ccache
timeout-minutes: 25
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential zlib1g-dev"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache@v4
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
@@ -111,11 +119,10 @@ jobs:
- name: Enable ccache
run: |
set -eu
command -v ccache >/dev/null 2>&1 || sudo apt-get install -y -qq ccache
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 500M
ccache -M 250M
ccache -z
- name: Build RetroArch headless
@@ -161,6 +168,23 @@ jobs:
./samples/runloop/content_closing/content_closing_test
make -C samples/runloop/content_closing clean
- name: Build and run cloudsync_unload_test
shell: bash
run: |
set -eu
# Closing content with Cloud Sync in automatic mode asks for
# a sync; it has to start once the content's save RAM is on
# disk and no core owns the file, which a close from the
# menu reaches a frame or more later. The real retroarch.c,
# a core with save RAM, and task_push_cloud_sync wrapped at
# link time to record what the save file held when the sync
# was asked for. No server is contacted.
#
# Shares the non-Qt build made above.
samples/runloop/cloudsync_unload/build.sh
./samples/runloop/cloudsync_unload/cloudsync_unload_test
make -C samples/runloop/cloudsync_unload clean
- name: Build and run menu_playlist_nav_test
shell: bash
run: |
@@ -200,6 +224,61 @@ jobs:
./samples/menu/nav_repeat_stall/menu_nav_repeat_stall_test
make -C samples/menu/nav_repeat_stall clean
- name: Build and run menu_icon_ownership_test
shell: bash
run: |
set -eu
# Every texture handle the menu drivers hold, accounted for
# against a counting null driver: a handle unloaded twice or
# still live at deinit fails the lane. Ozone's sidebar wrote
# the shared cursor texture into a playlist node whose own
# icon had not landed, and the context destroy then freed the
# cursor twice - a heap-corruption crash at startup under
# threaded video. MaterialUI's playlist icon array could be
# rebuilt under a decode still in flight. The contentless
# cores list's icons, fallback and per core, go at deinit.
# Shares the non-Qt build made above; one binary, one run per
# lane.
samples/menu/menu_icon_ownership/build.sh
./samples/menu/menu_icon_ownership/menu_icon_ownership_test ozone
./samples/menu/menu_icon_ownership/menu_icon_ownership_test xmb
./samples/menu/menu_icon_ownership/menu_icon_ownership_test materialui
./samples/menu/menu_icon_ownership/menu_icon_ownership_test contentless
make -C samples/menu/menu_icon_ownership clean
- name: Build and run ozone_sublabel_scroll_test
shell: bash
run: |
set -eu
# Ozone with sublabels on the selected entry only: a selection
# change asking for no animation moves the list at once, an
# animated one tweens and fades the cursor once, the list
# behind the sidebar follows a new selection, and a selection
# past the end is not taken for a new one on every compute.
# Shares the non-Qt build made above.
samples/menu/ozone_sublabel_scroll/build.sh
./samples/menu/ozone_sublabel_scroll/ozone_sublabel_scroll_test
make -C samples/menu/ozone_sublabel_scroll clean
- name: Build and run menu_mouse_click_test
shell: bash
run: |
set -eu
# A left click in the menu has to select the entry under the
# pointer. The pen support gave every pointer that is not a
# touchscreen no gesture on release, and a mouse's left click
# reaches the menu drivers as a tap gesture, so the click was
# thrown away in every menu driver while movement, the wheel
# and the right button kept working. Drives the real runloop
# with a scripted mouse: points at Settings by asking the menu
# which entry is under the pointer, opens it with a left
# click, scrolls its list with the wheel, goes back with a
# right click, and checks a long press is not a click. Shares
# the non-Qt build made above.
samples/menu/mouse_click/build.sh
./samples/menu/mouse_click/menu_mouse_click_test
make -C samples/menu/mouse_click clean
- name: Build and run menu_network_settings_test
shell: bash
run: |
@@ -241,6 +320,19 @@ jobs:
./samples/settings/packed_rows/packed_rows_test
make -C samples/settings/packed_rows clean
- name: Build and run setting_command_test
shell: bash
run: |
set -eu
# A press on a settings row whose command reinitialises the
# video driver has to bring the driver up once, on the first
# press and on every later one, whether or not the row
# applies its command by itself. Shares the non-Qt build
# made above.
samples/menu/setting_command/build.sh
./samples/menu/setting_command/setting_command_test
make -C samples/menu/setting_command clean
- name: Hot function budget
shell: bash
run: |
@@ -263,6 +355,12 @@ jobs:
# build has no Vulkan, so the Vulkan entries report SKIP.
tools/lock_acquire_budget.sh
- name: Wait budget
run: |
# Main-thread waits on the video thread, per file, against
# tools/wait_budget.ref: a count that only comes down.
tools/wait_budget.sh
- name: Build and run threaded_video_test
shell: bash
run: |
@@ -296,6 +394,49 @@ jobs:
./samples/tasks/content_load/content_load_test
make -C samples/tasks/content_load clean
- name: Build and run video_filter_menu_test
shell: bash
run: |
set -eu
# The menu's Video Filter and Video Filter Enable rows get the
# driver the frames they make - a chosen filter's scale and
# format, the core's or the filter's format as Enable turns it
# off and on: told in place to a driver that takes it
# (set_frame_format), set up again otherwise, and nothing at
# all when the frames do not change.
samples/gfx/video_filter_menu/build.sh
./samples/gfx/video_filter_menu/video_filter_menu_test
# Under the threaded wrapper too: a filter swapped without
# waiting for the video thread, the old one freed behind the
# frames staged with it
HARNESS_THREADED=1 ./samples/gfx/video_filter_menu/video_filter_menu_test
make -C samples/gfx/video_filter_menu clean
- name: Build and run core_info_page_test
shell: bash
run: |
set -eu
# The running core's information - its current-core entry,
# Core Information in the Information menu and the page
# itself - comes from its info file, once it is up and again
# after the drivers are set up again under it.
samples/menu/core_info_page/build.sh
./samples/menu/core_info_page/core_info_page_test
make -C samples/menu/core_info_page clean
- name: Build and run load_content_filter_test
shell: bash
run: |
set -eu
# Load Content with no core lists every file an installed core
# or a built-in player opens, however long the installed cores'
# extensions run together. Content Scan's Content Location
# lists files and folders, and Import Content offers Content
# Scan alone.
samples/menu/load_content_filter/build.sh
./samples/menu/load_content_filter/load_content_filter_test
make -C samples/menu/load_content_filter clean
- name: Build and run explore_build_test
shell: bash
run: |
@@ -306,3 +447,31 @@ jobs:
samples/menu/explore_build/build.sh
./samples/menu/explore_build/explore_build_test
make -C samples/menu/explore_build clean
- name: Frame command progress through the real runloop
shell: bash
run: |
set -eu
make -C samples/network/command_frames check
make -C samples/network/command_frames clean
- name: Structured HDR queries through the real frontend
shell: bash
run: |
set -eu
make -j$(nproc)
python3 samples/gfx/hdr_metadata/live.py --retroarch ./retroarch --log-dir /tmp/hdr-metadata-live
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+258
View File
@@ -0,0 +1,258 @@
name: CI fullscreen media menu
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-menu-media.yml'
# ci_paths: end
pull_request:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-menu-media.yml'
# ci_paths: end
workflow_dispatch:
permissions:
contents: read
jobs:
rgui-media:
runs-on: ubuntu-latest
env:
APT_PKGS: ffmpeg
timeout-minutes: 20
strategy:
matrix:
threads: [enable, disable]
steps:
- uses: actions/checkout@v6
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Configure C89 frontend
run: ./configure --disable-qt --disable-vulkan --disable-wayland --disable-x11 --disable-opengl --disable-opengl1 --disable-opengl_core --disable-ffmpeg --disable-cheevos --disable-webmplayer --${{ matrix.threads }}-threads
- name: Build shipping frontend
run: make -j2 C89_BUILD=1
- name: Generate movie fixture
run: ffmpeg -v error -y -f lavfi -i testsrc2=size=320x240:rate=30 -f lavfi -i sine=frequency=440:sample_rate=48000 -t 3 -c:v libx264 -preset ultrafast -pix_fmt yuv420p -c:a aac -shortest samples/menu/rgui_media/fixture.mp4
- name: Real RGUI framebuffer and playback controls
run: python3 samples/menu/rgui_media/run.py --c89 --cadence-hz 30 samples/menu/rgui_media/fixture.mp4
- name: Real MaterialUI file-browser playback controls
run: python3 samples/menu/rgui_media/run.py --c89 --cadence-hz 30 --driver materialui samples/menu/rgui_media/fixture.mp4
- name: Real XMB file-browser playback controls
run: python3 samples/menu/rgui_media/run.py --c89 --cadence-hz 30 --driver xmb samples/menu/rgui_media/fixture.mp4
- name: Real Ozone file-browser playback controls
run: python3 samples/menu/rgui_media/run.py --c89 --cadence-hz 30 --driver ozone samples/menu/rgui_media/fixture.mp4
- name: UTF-8 subtitle sidecars across all four menus
run: |
python3 -c "from pathlib import Path; Path('samples/menu/rgui_media/fixture.srt').write_text('1\n00:00:00,000 --> 00:10:00,000\nSubtitle sidecar\nUTF-8 \u00e9\n\n', encoding='utf-8')"
python3 -c "from pathlib import Path; Path('samples/menu/rgui_media/fixture.en.srt').write_text('1\n00:00:00,000 --> 00:10:00,000\nEnglish track\n', encoding='utf-8')"
for driver in rgui materialui xmb ozone; do
python3 samples/menu/rgui_media/run.py --c89 --subtitles --driver "$driver" samples/menu/rgui_media/fixture.mp4
done
- name: Embedded MP4 subtitles across all four menus
run: |
python3 -c "from pathlib import Path; [Path('samples/menu/rgui_media/embedded-'+n+'.srt').write_text('1\n00:00:00,000 --> 00:10:00,000\nEmbedded '+n+' UTF-8 \u00e9\n', encoding='utf-8') for n in ('first', 'second')]"
ffmpeg -v error -y -i samples/menu/rgui_media/fixture.mp4 -i samples/menu/rgui_media/embedded-first.srt -i samples/menu/rgui_media/embedded-second.srt -map 0:v:0 -map 1:0 -map 2:0 -c:v copy -c:s mov_text samples/menu/rgui_media/embedded.mp4
for driver in rgui materialui xmb ozone; do
MEDIA_TEST_EMBEDDED_SUBTITLES=1 python3 samples/menu/rgui_media/run.py --c89 --driver "$driver" samples/menu/rgui_media/embedded.mp4
done
- name: Embedded Matroska subtitles across all four menus
run: |
ffmpeg -v error -y -f lavfi -i testsrc2=size=320x240:rate=30 -i samples/menu/rgui_media/embedded-first.srt -i samples/menu/rgui_media/embedded-second.srt -map 0:v:0 -map 1:0 -map 2:0 -t 4 -c:v libvpx-vp9 -deadline realtime -cpu-used 8 -g 30 -auto-alt-ref 0 -threads 1 -c:s srt samples/menu/rgui_media/embedded.mkv
for driver in rgui materialui xmb ozone; do
MEDIA_TEST_EMBEDDED_SUBTITLES=1 python3 samples/menu/rgui_media/run.py --c89 --driver "$driver" samples/menu/rgui_media/embedded.mkv
done
- name: Folder playback and fullscreen settings across all menus
run: |
ffmpeg -v error -y -i samples/menu/rgui_media/embedded.mkv -map 0:v:0 -c:v copy samples/menu/rgui_media/folder.mkv
for driver in rgui materialui xmb ozone; do
python3 samples/menu/rgui_media/run.py --c89 --desktop-refresh --driver "$driver" samples/menu/rgui_media/fixture.mp4
python3 samples/menu/rgui_media/run.py --c89 --continuous-error --driver "$driver" samples/menu/rgui_media/fixture.mp4
python3 samples/menu/rgui_media/run.py --c89 --continuous --driver "$driver" samples/menu/rgui_media/folder.mkv
done
- name: External movies and internal image activation across all menus
run: |
ffmpeg -v error -y -f lavfi -i color=c=orange:size=32x32 -frames:v 1 -threads 1 samples/menu/rgui_media/fixture.png
for driver in rgui materialui xmb ozone; do
python3 samples/menu/rgui_media/run.py --c89 --external --driver "$driver" samples/menu/rgui_media/fixture.mp4
python3 samples/menu/rgui_media/run.py --c89 --external --driver "$driver" samples/menu/rgui_media/fixture.png
python3 samples/menu/rgui_media/run.py --c89 --browser-image --driver "$driver" samples/menu/rgui_media/fixture.png
done
- name: Background setting browser picks images instead of viewing them
run: |
ffmpeg -v error -y -f lavfi -i color=c=orange:size=32x32 -frames:v 1 -threads 1 samples/menu/rgui_media/fixture.png
for driver in xmb materialui; do
python3 samples/menu/rgui_media/run.py --c89 --wallpaper-pick --driver "$driver" samples/menu/rgui_media/fixture.png
done
- name: Images history entries open in the viewer and Back returns to the list
run: |
ffmpeg -v error -y -f lavfi -i color=c=orange:size=32x32 -frames:v 1 -threads 1 samples/menu/rgui_media/fixture.png
for driver in rgui materialui xmb ozone; do
python3 samples/menu/rgui_media/run.py --c89 --history-image --driver "$driver" samples/menu/rgui_media/fixture.png
done
- name: Generate WebM transport fixtures
run: |
ffmpeg -v error -y -f lavfi -i testsrc2=size=320x240:rate=30 -f lavfi -i sine=frequency=440:sample_rate=48000 -t 3 -c:v libvpx -deadline realtime -cpu-used 8 -g 30 -auto-alt-ref 0 -c:a libopus -shortest samples/menu/rgui_media/fixture-opus.webm
ffmpeg -v error -y -f lavfi -i testsrc2=size=320x240:rate=30 -f lavfi -i sine=frequency=440:sample_rate=48000 -t 3 -c:v libvpx-vp9 -g 30 -auto-alt-ref 0 -c:a libvorbis -shortest samples/menu/rgui_media/fixture-vorbis.webm
- name: Real WebM controls across all four menus
run: |
for driver in rgui materialui xmb ozone; do
python3 samples/menu/rgui_media/run.py --c89 --driver "$driver" samples/menu/rgui_media/fixture-opus.webm
python3 samples/menu/rgui_media/run.py --c89 --driver "$driver" samples/menu/rgui_media/fixture-vorbis.webm
done
window-toggle-media:
runs-on: ubuntu-latest
timeout-minutes: 20
env:
APT_PKGS: ffmpeg xvfb xauth libgl1-mesa-dev libx11-dev libxext-dev libxrandr-dev
steps:
- uses: actions/checkout@v6
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Configure C89 GL frontend
run: ./configure --disable-qt --disable-vulkan --disable-wayland --disable-ffmpeg --disable-cheevos --disable-webmplayer
- name: Build shipping frontend
run: make -j2 C89_BUILD=1
- name: Generate moving VP8 fixture
run: |
ffmpeg -v error -y -f lavfi -i "testsrc2=size=320x240:rate=30,hue=h=90*t" -t 3 -c:v libvpx -deadline realtime -cpu-used 8 -g 90 -auto-alt-ref 0 samples/menu/rgui_media/window-toggle.webm
ffmpeg -v error -y -f lavfi -i "color=orange:size=320x240:rate=30" -t 3 -c:v libvpx -deadline realtime -cpu-used 8 -g 90 -auto-alt-ref 0 samples/menu/rgui_media/settings-preview.webm
- name: External media starts without browser frames and recovers from failure
run: |
set -eu
ffmpeg -v error -y -f lavfi -i color=c=orange:size=32x32 -frames:v 1 -threads 1 samples/menu/rgui_media/startup.png
python3 -c "from pathlib import Path; root = Path('samples/menu/rgui_media'); [(root / name).write_bytes(b'not decodable media') for name in ('corrupt.mp4', 'corrupt.png')]"
for driver in rgui materialui xmb ozone; do
for threaded in '' --threaded; do
for fixture in window-toggle.webm startup.png; do
xvfb-run -a python3 samples/menu/rgui_media/run.py --c89 --external --first-frame --video-driver gl --driver "$driver" $threaded "samples/menu/rgui_media/$fixture"
done
for fixture in corrupt.mp4 corrupt.png; do
xvfb-run -a python3 samples/menu/rgui_media/run.py --c89 --external --startup-failure --video-driver gl --driver "$driver" $threaded "samples/menu/rgui_media/$fixture"
done
done
done
- name: Fullscreen window transitions retain movie pixels and seeking
run: |
for driver in rgui materialui xmb ozone; do
for threaded in '' --threaded; do
xvfb-run -a python3 samples/menu/rgui_media/run.py --c89 --window-toggle --video-driver gl --driver "$driver" $threaded samples/menu/rgui_media/window-toggle.webm
done
done
- name: GPU folder playback and fullscreen settings across all menus
run: |
for driver in rgui materialui xmb ozone; do
for threaded in '' --threaded; do
MEDIA_TEST_SETTINGS_ORANGE=1 xvfb-run -a python3 samples/menu/rgui_media/run.py --c89 --fullscreen-settings --video-driver gl --driver "$driver" $threaded samples/menu/rgui_media/settings-preview.webm
xvfb-run -a python3 samples/menu/rgui_media/run.py --c89 --continuous-error --video-driver gl --driver "$driver" $threaded samples/menu/rgui_media/window-toggle.webm
done
done
- name: Generate delayed first-picture fixture
run: ffmpeg -v error -y -f lavfi -i "testsrc2=size=320x240:rate=30,hue=h=90*t" -vf "setpts=PTS+0.2/TB" -t 3 -c:v libvpx -deadline realtime -cpu-used 8 -g 90 -auto-alt-ref 0 samples/menu/rgui_media/delayed.webm
- name: Compare actual visible pictures after delayed-start seeks
run: |
for driver in rgui materialui xmb ozone; do
xvfb-run -a python3 samples/menu/rgui_media/run.py --c89 --video-driver gl --driver "$driver" --first-video-us 200000 samples/menu/rgui_media/delayed.webm
done
subtitle-sidecars:
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential clang ffmpeg
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: SubRip parsing, task ownership and drawing (GCC and Clang sanitizers)
shell: bash
run: |
set -eu
for compiler in gcc clang; do
for sample in libretro-common/samples/formats/subtitle_srt samples/tasks/subtitle_load samples/gfx/media_subtitle samples/gfx/media_controls; do
extra_flags=''
if [ "$compiler" = clang ]; then extra_flags="-Wno-c99-extensions -Wno-c11-extensions"; fi
make -C "$sample" clean
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1 CFLAGS="$extra_flags" \
make -C "$sample" check CC="$compiler" SANITIZER=address,undefined
done
done
- name: Subtitle ownership and publication races
shell: bash
run: |
set -eu
for sample in samples/tasks/subtitle_load samples/gfx/media_subtitle samples/gfx/media_controls; do
make -C "$sample" clean
TSAN_OPTIONS=halt_on_error=1 CFLAGS="-Wno-c99-extensions -Wno-c11-extensions" \
make -C "$sample" check CC=clang SANITIZER=thread
done
- name: Threadless subtitle loading and drawing
shell: bash
run: |
set -eu
for sample in libretro-common/samples/formats/subtitle_srt samples/tasks/subtitle_load samples/gfx/media_subtitle samples/gfx/media_controls; do
make -C "$sample" clean
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1 \
make -C "$sample" check THREADS=0 SANITIZER=address,undefined
done
+159
View File
@@ -0,0 +1,159 @@
name: CI Visual C++ 6 project (stand-in compiler)
on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-msvc6-standin.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-msvc6-standin.yml'
# ci_paths: end
workflow_dispatch:
permissions:
contents: read
env:
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
msvc6-standin:
name: The Visual C++ 6 project's sources, defines and libraries, on mingw held to old C
runs-on: ubuntu-latest
env:
APT_PKGS: --no-install-recommends gcc-mingw-w64-i686 g++-mingw-w64-i686 gcc-mingw-w64-x86-64 wine64 pulseaudio xvfb
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 15
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Check the VC8 dialog optimizer workaround
run: python3 tools/msvc2005_dialog_check.py
- name: Compile and link what pkg/msvc/msvc-6 builds
shell: bash
run: |
set -eu
# Visual C++ 6 cannot be fetched here, so nothing built its
# project. This reads the project file - sources, defines,
# libraries - and builds the same with a 32-bit mingw
# compiler under old-C rules, at Windows NT 4.0's API, and
# links it. See the script for what that does and does not
# catch.
tools/msvc6_standin_check.sh
- name: The DXGI bridge as Visual C++ 2005 and 2010 build it
shell: bash
run: |
set -eu
# Their SDKs have no dxgi1_5.h or d3d12.h; the griffin builds
# for them still compile and link the bridge.
tools/dxgi_bridge_old_sdk_check.sh
- name: Run the waveout audio driver on a wave output device (Wine)
working-directory: samples/audio/waveout
shell: bash
run: |
set -eu
# The audio driver the Visual C++ 6 project uses: the
# multimedia library's wave output, which is all a Windows
# with no DirectX has. Run for real against a device - a
# silent one, from a sound server started here - and held to
# what the audio interface says of a driver. With no device
# the test only skips, which checks nothing: that is a
# failure here.
trap 'wineserver -k >/dev/null 2>&1 || true; pulseaudio --kill >/dev/null 2>&1 || true' EXIT
pulseaudio --start --exit-idle-time=-1
pactl load-module module-null-sink sink_name=ci >/dev/null
pactl set-default-sink ci
WINE=$(command -v wine64 || command -v wine || echo /usr/lib/wine/wine64)
make clean && make -j$(nproc) all
WINEDEBUG=-all timeout 300 make check WINE="$WINE" | tee waveout.log
grep -q "PASS waveout_test" waveout.log
make clean
rm -f waveout.log
- name: Run the plain Win32 input driver against a window (Wine)
working-directory: samples/input/win32_input
shell: bash
run: |
set -eu
# The input driver the Visual C++ 6 project uses: keys from
# the window's messages, the mouse from where the cursor is.
# Run against a real window: its key map, keys down and up,
# nothing down with another window in front, the mouse's
# place and motion. A part it had to skip - no window, or one
# that would not come to the front - checks nothing, and is a
# failure here.
trap 'wineserver -k >/dev/null 2>&1 || true' EXIT
WINE=$(command -v wine64 || command -v wine || echo /usr/lib/wine/wine64)
make clean && make -j$(nproc) all
WINEDEBUG=-all timeout 300 make check WINE="$WINE" | tee win32_input.log
grep -q "PASS win32_input_test" win32_input.log
if grep -q "^\[skip\]" win32_input.log; then
echo "a part of the test was skipped"
exit 1
fi
make clean
rm -f win32_input.log
- name: Run the winmm joypad driver against stand-in joystick calls (Wine)
working-directory: samples/input/winmm_joypad
shell: bash
run: |
set -eu
# The joypad driver the Visual C++ 6 project uses: the
# multimedia library's joystick calls. No controller can be
# scripted here, so the calls and the clock are the test's:
# axes across the controller's own ranges, thirty-two
# buttons, the hat's eight ways, the RetroPad mask, a
# controller pulled out and plugged in, and the controllers
# that are not there not asked at every poll.
trap 'wineserver -k >/dev/null 2>&1 || true' EXIT
WINE=$(command -v wine64 || command -v wine || echo /usr/lib/wine/wine64)
make clean && make -j$(nproc) all
WINEDEBUG=-all timeout 300 make check WINE="$WINE" | tee winmm_joypad.log
grep -q "PASS winmm_joypad_test" winmm_joypad.log
make clean
rm -f winmm_joypad.log
+94
View File
@@ -0,0 +1,94 @@
name: CI Linux netplay end to end
on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- 'samples/network/netplay_e2e/**'
- '.github/workflows/Linux-netplay-e2e.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- 'samples/network/netplay_e2e/**'
- '.github/workflows/Linux-netplay-e2e.yml'
# ci_paths: end
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
netplay-e2e:
name: Two retroarch processes in netplay
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential pkg-config zlib1g-dev python3 ccache
timeout-minutes: 40
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Build RetroArch with no menu, video or sound
shell: bash
run: |
set -eu
./configure --disable-qt --disable-ffmpeg --disable-vulkan \
--disable-wayland --disable-x11 --disable-opengl --disable-egl \
--disable-kms --disable-sdl2 --disable-pulse --disable-alsa \
--disable-jack --disable-oss --disable-freetype --disable-menu
grep -q '^HAVE_NETWORKGAMEPAD = 1' config.mk
make -j"$(nproc)" CC="ccache gcc" CXX="ccache g++"
- name: Host and client over the loopback
shell: bash
run: |
set -eu
# analog sticks synced and not, lockstep asked for, a lockstep
# host with a rollback client, and a state too large to roll
# back; the peers' RAM agrees on every frame
make -C samples/network/netplay_e2e
timeout 600 make -C samples/network/netplay_e2e check RETROARCH=$PWD/retroarch
make -C samples/network/netplay_e2e clean
+118 -32
View File
@@ -10,9 +10,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-network-clients.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-network-clients.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -26,42 +38,43 @@ jobs:
network-clients:
name: TLS, SMB and NFS clients against local servers
runs-on: ubuntu-latest
env:
APT_PKGS: --no-install-recommends build-essential clang gcc-mingw-w64-x86-64 openssl samba libtirpc-dev flex bison autoconf automake wine wine64 nfs-ganesha nfs-ganesha-vfs krb5-kdc krb5-admin-server krb5-user winbind samba-dsdb-modules samba-ad-provision samba-vfs-modules acl smbclient tshark
timeout-minutes: 45
steps:
- name: Install dependencies
timeout-minutes: 25
run: |
sudo apt-get update -y
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
# tshark's dumpcap asks whether to be setuid: no, runs are root
echo "wireshark-common wireshark-common/install-setuid boolean false" | sudo debconf-set-selections
export DEBIAN_FRONTEND=noninteractive
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only --no-install-recommends build-essential clang \
gcc-mingw-w64-x86-64 openssl samba libtirpc-dev flex bison autoconf automake wine wine64 \
nfs-ganesha nfs-ganesha-vfs krb5-kdc krb5-admin-server krb5-user winbind \
samba-dsdb-modules samba-ad-provision samba-vfs-modules acl smbclient tshark && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y --no-install-recommends build-essential clang \
gcc-mingw-w64-x86-64 openssl samba libtirpc-dev flex bison autoconf automake wine wine64 \
nfs-ganesha nfs-ganesha-vfs krb5-kdc krb5-admin-server krb5-user winbind \
samba-dsdb-modules samba-ad-provision samba-vfs-modules acl smbclient tshark
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Preseed tshark
# dumpcap asks whether to be setuid: no, runs are root
run: echo "wireshark-common wireshark-common/install-setuid boolean false" | sudo debconf-set-selections
- name: Install dependencies
timeout-minutes: 25
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Built-in TLS client against a local openssl server
shell: bash
working-directory: ${{ github.workspace }}
@@ -94,6 +107,59 @@ jobs:
sudo samples/network/smb_retro/local_ad_dc_test.sh
sudo make -C samples/network/smb_retro clean
- name: Embedded MCP server over the loopback
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# network/mcp_server.c as it ships, in front of a stub command
# table: the handshake, the tool list and its hints, answers now
# and later, the JSON-RPC errors, the HTTP gate (method, path,
# Origin, the bearer token it will not start without), an
# oversized body, a request in pieces, and teardown with a
# request waiting
make -C samples/network/mcp_server clean
make -C samples/network/mcp_server SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1 \
make -C samples/network/mcp_server check
make -C samples/network/mcp_server clean
- name: Netplay input bookkeeping
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# netplay_frontend.c's input decode, real-client mask, analog
# joypad ports and relay host setup, as they ship
make -C samples/network/netplay_input clean
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1 \
make -C samples/network/netplay_input check SANITIZER=address,undefined
make -C samples/network/netplay_input clean
make -C samples/network/netplay_input CC=x86_64-w64-mingw32-gcc LDFLAGS=-static
make -C samples/network/netplay_input clean
- name: GET_LOG's ring, written from four threads while read
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
make -C samples/runloop/log_ring clean
make -C samples/runloop/log_ring SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1 \
make -C samples/runloop/log_ring check
make -C samples/runloop/log_ring clean
- name: Command options and speed setters
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
make -C samples/network/command_options clean
make -C samples/network/command_options SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1 \
make -C samples/network/command_options check
make -C samples/network/command_options clean
- name: Windows builds under Wine
shell: bash
@@ -106,7 +172,26 @@ jobs:
export WINEDEBUG=-all WINEPREFIX=$RUNNER_TEMP/wineprefix
# the tools below are run through wine, so it must be there
command -v wine >/dev/null
# One wineserver for the whole step. Ubuntu's wrapper starts
# it with -p0: it exits with each tool and the next tool
# starts another. A tool that starts while the last server
# is still on its way out is cut off by it before main()
# ("wine client error:0: recvmsg: Connection reset by peer")
# and the lane fails with nothing wrong in the tree. The
# first process brings up Wine's services, which keep its
# output for as long as they live, so it is given none.
mkdir -p "$WINEPREFIX"
wineserver -p
trap 'wineserver -k || true; sudo -E env HOME=/root WINEPREFIX=/tmp/wineprefix-root wineserver -k || true' EXIT
wine cmd /c exit > /dev/null 2>&1 < /dev/null
make -C libretro-common -f Makefile.test win32-wine-check
# the MCP server on Winsock
make -C samples/network/mcp_server clean
make -C samples/network/mcp_server compiler=x86_64-w64-mingw32-gcc
(cd samples/network/mcp_server && wine ./mcp_server_test.exe)
make -C samples/network/mcp_server clean
make -C samples/network/command_options check compiler=x86_64-w64-mingw32-gcc RUNNER=wine
make -C samples/network/command_options clean
export CC=x86_64-w64-mingw32-gcc RUNNER=wine EXE=.exe
make -C samples/network/tls_retro clean
samples/network/tls_retro/local_server_test.sh
@@ -116,7 +201,8 @@ jobs:
# owned by someone else and creating one inside a directory it
# does not own ($RUNNER_TEMP belongs to the runner user), so the
# root run gets a prefix under /tmp with root's own HOME
sudo -E env HOME=/root WINEPREFIX=/tmp/wineprefix-root wineboot -u
sudo -E env HOME=/root WINEPREFIX=/tmp/wineprefix-root sh -c 'mkdir -p "$WINEPREFIX" && wineserver -p'
sudo -E env HOME=/root WINEPREFIX=/tmp/wineprefix-root wineboot -u > /dev/null 2>&1 < /dev/null
sudo -E env HOME=/root WINEPREFIX=/tmp/wineprefix-root samples/network/smb_retro/local_samba_test.sh
sudo -E env HOME=/root WINEPREFIX=/tmp/wineprefix-root samples/network/smb_retro/local_ad_dc_test.sh
sudo make -C samples/network/smb_retro clean
@@ -159,7 +245,7 @@ jobs:
# for memory errors, undefined behaviour and leaks. UBSan found
# a NULL memcpy in the SHA-2 stream updates on the SHA-384 PRF
# path the first time this ran.
export CFLAGS="-O1 -g -fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer"
export CFLAGS="-O1 -g1 -fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer"
export LDFLAGS="-fsanitize=address,undefined"
export ASAN_OPTIONS=detect_leaks=1:strict_string_checks=1
export UBSAN_OPTIONS=print_stacktrace=1
+45 -11
View File
@@ -4,9 +4,27 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- 'samples/frontend/dbus_runtime/**'
- '.github/workflows/Linux-no-pkg-config.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- 'samples/frontend/dbus_runtime/**'
- '.github/workflows/Linux-no-pkg-config.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -23,23 +41,39 @@ jobs:
no-pkg-config:
name: Configure and build with pkg-config unavailable
runs-on: ubuntu-latest
env:
APT_PKGS: --no-install-recommends build-essential zlib1g-dev libfreetype-dev libdrm-dev libasound2-dev libpulse-dev libpipewire-0.3-dev libdbus-1-dev libwayland-dev libwayland-bin libxkbcommon-dev libdecor-0-dev libssl-dev qt6-base-dev qt6-base-dev-tools
timeout-minutes: 30
steps:
- name: Install dependencies
shell: bash
run: |
set -eu
sudo apt-get update -qq
sudo apt-get install -y -qq --no-install-recommends \
build-essential zlib1g-dev libfreetype-dev libdrm-dev \
libasound2-dev libpulse-dev libpipewire-0.3-dev \
libdbus-1-dev libwayland-dev libwayland-bin libxkbcommon-dev \
libdecor-0-dev libssl-dev qt6-base-dev qt6-base-dev-tools
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 15
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Configure
shell: bash
run: |
+353
View File
@@ -0,0 +1,353 @@
name: CI OpenXR (stand-in runtime, desktop headset path)
on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-openxr-input.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-openxr-input.yml'
# ci_paths: end
workflow_dispatch:
permissions:
contents: read
env:
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
openxr-input:
name: OpenXR input and shared units against a stand-in runtime
runs-on: ubuntu-latest
env:
APT_PKGS: pkg-config libegl-dev libgl-dev libx11-dev
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Run input/common/input_openxr.c against a stand-in runtime
working-directory: samples/input/openxr_input
shell: bash
run: |
set -euo pipefail
# The headset's controllers, the desktop's and the Quest's, as
# they ship, against a runtime and a frontend the test stands
# in for: what the actions give the RetroPad, Touch's menu
# button, the laser on a frame and on the menu, rumble, and
# nothing held when the runtime has nothing to give.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
./openxr_input_test | tee openxr_input.log
grep -q "PASS openxr_input_test" openxr_input.log
make clean
rm -f openxr_input.log
- name: Run the shared OpenXR units against a stand-in runtime
working-directory: samples/openxr/core
shell: bash
run: |
set -euo pipefail
# The Makefile leaves the desktop GL host's tests out where
# these are not found, and the run still passes.
pkg-config --exists egl x11 gl
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
./openxr_core_test | tee openxr_core.log
grep -q "PASS openxr_core_test" openxr_core.log
make clean
rm -f openxr_core.log
- name: Run the shared OpenXR units under TSan
working-directory: samples/openxr/core
shell: bash
run: |
set -euo pipefail
# The XR thread, the tick waiters and the lock gaps.
pkg-config --exists egl x11 gl
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
./openxr_core_test | tee openxr_core.log
grep -q "PASS openxr_core_test" openxr_core.log
make clean
rm -f openxr_core.log
openxr-haptics:
name: Desktop haptic results (Linux sanitizers, Windows build)
runs-on: ubuntu-latest
env:
APT_PKGS: gcc-mingw-w64-x86-64
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Run desktop haptics against scripted runtime results
working-directory: samples/input/openxr_haptics
shell: bash
run: |
set -eu
make clean
make SANITIZER=address,undefined check
make clean
make SANITIZER=thread check
make clean
make CC=x86_64-w64-mingw32-gcc all
make clean
openxr-loader-threads:
name: OpenXR loader under concurrent use (TSan, ASan+UBSan)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Run deps/openxr's loader from several threads
working-directory: samples/openxr/loader_threads
shell: bash
run: |
set -eu
# The loader shares its runtime, active instance, recorder and
# messenger tables and property set between threads with no
# lock. Threads enumerate, create, reinitialize and log against
# a stand-in runtime while the main thread cycles instances and
# messengers; a race or a use-after-free in any of it fails.
make clean
make SANITIZER=thread check
make clean
make SANITIZER=address,undefined check
make clean
openxr-desktop:
name: Desktop headset output with the in-tree loader
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential pkg-config libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libx11-dev libxext-dev libxrandr-dev libxinerama-dev libxxf86vm-dev libxv-dev libxkbcommon-dev libwayland-dev wayland-protocols libdrm-dev libgbm-dev libasound2-dev libpulse-dev libudev-dev libusb-1.0-0-dev libvulkan-dev libdbus-1-dev zlib1g-dev
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Build RetroArch with OpenXR
shell: bash
run: |
set -eu
# OpenXR is opt-in; the vendored Khronos loader is built into
# the tree, so nothing has to be installed. Qt has to go: the
# menu harness below replaces main(), which lives in ui_qt.o
# with Qt enabled.
# The screens' windows too, which video_screens_test runs.
./configure --disable-qt --enable-openxr --enable-video_windows
grep -q '^HAVE_OPENXR = 1' config.mk
grep -q '^HAVE_VIDEO_WINDOWS = 1' config.mk
make -j$(nproc)
# Vulkan's headset host and the desktop GL drivers', side by
# side.
test -f obj-unix/release/gfx/common/vulkan_openxr.o
test -f obj-unix/release/gfx/common/gl_openxr.o
test -f obj-unix/release/gfx/common/openxr_binding_gl.o
- name: Build and run video_screens_test
shell: bash
run: |
set -eu
python3 samples/settings/video_screens/build.py \
--output-dir "$RUNNER_TEMP/video-screens"
- name: Build and run stereo_headset_test
shell: bash
run: |
set -eu
# The Stereo 3D & Screens list, with Headset Output off and
# on under each driver that has a headset path, against the
# shipping menu objects.
samples/menu/stereo_headset/build.sh
./samples/menu/stereo_headset/stereo_headset_test
make -C samples/menu/stereo_headset clean
- name: Run the headset end-to-end runner's own tests
shell: bash
run: |
set -eu
# samples/openxr/e2e/run.py needs a runtime and a GPU; its
# case selection and its OpenGL lanes do not.
python3 -m unittest discover -s samples/openxr/e2e -p 'test_*.py'
- name: Build the OpenXR test layer and the video_views core
shell: bash
run: |
set -eu
# The API layer the Monado end-to-end check records through,
# against the vendored headers, and the core it drives.
make -C samples/openxr/test_layer clean && make -C samples/openxr/test_layer -j$(nproc) all
test -f samples/openxr/test_layer/libxr_test_layer.so
make -C samples/cores/video_views clean && make -C samples/cores/video_views -j$(nproc) all
test -f samples/cores/video_views/video_views_libretro.so
make -C samples/openxr/test_layer clean
make -C samples/cores/video_views clean
openxr-desktop-gl:
name: Desktop headset output on OpenGL alone, without Vulkan
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential pkg-config libgl1-mesa-dev libgles2-mesa-dev libegl1-mesa-dev libx11-dev libxext-dev libxrandr-dev libxinerama-dev libxxf86vm-dev libxv-dev libxkbcommon-dev libwayland-dev wayland-protocols libdrm-dev libgbm-dev libasound2-dev libpulse-dev libudev-dev libusb-1.0-0-dev libdbus-1-dev zlib1g-dev
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Build RetroArch with OpenXR and no Vulkan
shell: bash
run: |
set -eu
# The gl and glcore drivers' headset output has to configure,
# build and link without Vulkan.
./configure --disable-qt --enable-openxr --disable-vulkan \
--enable-video_windows
grep -q '^HAVE_OPENXR = 1' config.mk
grep -q '^HAVE_VIDEO_WINDOWS = 1' config.mk
grep -q '^HAVE_VULKAN = 0' config.mk
make -j$(nproc)
test -f obj-unix/release/gfx/common/openxr_frame.o
test -f obj-unix/release/gfx/common/gl_openxr.o
test ! -e obj-unix/release/gfx/common/vulkan_openxr.o
- name: Build and run video_screens_test
shell: bash
run: |
set -eu
python3 samples/settings/video_screens/build.py \
--output-dir "$RUNNER_TEMP/video-screens"
- name: Build and run stereo_headset_test
shell: bash
run: |
set -eu
# Headset Output under glcore and gl, with no vulkan driver
# to list it under.
samples/menu/stereo_headset/build.sh
./samples/menu/stereo_headset/stereo_headset_test
make -C samples/menu/stereo_headset clean
+93 -40
View File
@@ -24,12 +24,19 @@ on:
- 'tasks/task_overlay.c'
- 'led/drivers/led_overlay.c'
- 'gfx/gfx_surface.*'
- 'gfx/gfx_surface_player.*'
- 'libretro-common/formats/video_player.c'
- 'gfx/video_driver.c'
- 'gfx/video_thread_wrapper.*'
- 'gfx/drivers/gl2.c'
- 'menu/menu_driver.c'
- 'input/drivers/x11_input.c'
- 'samples/input/overlay_on_screen/**'
- 'input/input_driver_choice.c'
- 'gfx/drivers_context/x_ctx.c'
- 'gfx/common/x11_common.c'
- 'samples/input/x11_smoke/**'
- 'samples/input/windows_wine_smoke/smoke_core.c'
- '.github/workflows/Linux-overlay-on-screen.yml'
pull_request:
paths:
@@ -38,12 +45,19 @@ on:
- 'tasks/task_overlay.c'
- 'led/drivers/led_overlay.c'
- 'gfx/gfx_surface.*'
- 'gfx/gfx_surface_player.*'
- 'libretro-common/formats/video_player.c'
- 'gfx/video_driver.c'
- 'gfx/video_thread_wrapper.*'
- 'gfx/drivers/gl2.c'
- 'menu/menu_driver.c'
- 'input/drivers/x11_input.c'
- 'samples/input/overlay_on_screen/**'
- 'input/input_driver_choice.c'
- 'gfx/drivers_context/x_ctx.c'
- 'gfx/common/x11_common.c'
- 'samples/input/x11_smoke/**'
- 'samples/input/windows_wine_smoke/smoke_core.c'
- '.github/workflows/Linux-overlay-on-screen.yml'
workflow_dispatch:
@@ -61,52 +75,41 @@ jobs:
overlay-on-screen:
name: Run retroarch under Xvfb and look at the overlay
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential zlib1g-dev ccache python3 xvfb x11-apps xdotool libgl1-mesa-dri libx11-dev libx11-xcb-dev libxcb1-dev libxext-dev libxtst6 libxxf86vm-dev libxrandr-dev libxinerama-dev libgl-dev libegl-dev
timeout-minutes: 25
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
build-essential zlib1g-dev ccache python3 \
xvfb x11-apps libgl1-mesa-dri \
libx11-dev libx11-xcb-dev libxcb1-dev libxext-dev libxtst6 \
libxxf86vm-dev libxrandr-dev libxinerama-dev \
libgl-dev libegl-dev && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
build-essential zlib1g-dev ccache python3 \
xvfb x11-apps libgl1-mesa-dri \
libx11-dev libx11-xcb-dev libxcb1-dev libxext-dev libxtst6 \
libxxf86vm-dev libxrandr-dev libxinerama-dev \
libgl-dev libegl-dev
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache@v4
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
@@ -119,7 +122,7 @@ jobs:
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 500M
ccache -M 250M
ccache -z
- name: Build RetroArch (X11 + OpenGL, the rest off)
@@ -156,6 +159,36 @@ jobs:
# modes.
timeout 180 python3 samples/input/overlay_on_screen/overlay_on_screen_test.py ./retroarch
- name: Run overlay_apng_on_screen_test (an animated image plays and loops, threaded off and on)
shell: bash
run: |
set -eu
timeout 180 python3 samples/input/overlay_on_screen/overlay_apng_on_screen_test.py ./retroarch
- name: Run overlay_apng_press_on_screen_test (a two-frame image shows its pressed frame, threaded off and on)
shell: bash
run: |
set -eu
timeout 180 python3 samples/input/overlay_on_screen/overlay_apng_press_on_screen_test.py ./retroarch
- name: Run overlay_stick_on_screen_test (a movable stick follows the finger and returns, threaded off and on)
shell: bash
run: |
set -eu
# A movable stick's geometry is sent only when it moves; the
# picture shows nothing a move needed was left out.
timeout 180 python3 samples/input/overlay_on_screen/overlay_stick_on_screen_test.py ./retroarch
- name: Run statistics_on_screen_test (threaded video off, then on)
shell: bash
run: |
set -eu
# This build has no video filters, and the statistics have
# to show all the same: they are drawn with the OSD font,
# which reaches the video state through a binding video init
# makes whatever the build leaves out.
timeout 180 python3 samples/gfx/statistics_on_screen/statistics_on_screen_test.py ./retroarch
- name: Run overlay_menu_mouse_test (display and input packs, threaded off and on)
shell: bash
run: |
@@ -176,3 +209,23 @@ jobs:
# as does one named with _led, and a pack that names its LEDs
# is not mapped.
timeout 300 python3 samples/input/overlay_on_screen/overlay_led_hidden_test.py ./retroarch
- name: Run the X11 input scenarios (keys pressed in the window)
shell: bash
run: |
set -eu
# The same build, on a virtual display of its own, with the
# X11 input driver: keys are pressed in the window and a small
# core says in the log what reached it - before and after a
# fullscreen toggle restarts the video driver, with threaded
# video and without. See samples/input/x11_smoke/run.sh.
timeout 600 samples/input/x11_smoke/run.sh
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+44 -35
View File
@@ -19,9 +19,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-pace-shadow.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-pace-shadow.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -37,45 +49,42 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
env:
APT_PKGS: build-essential libxkbcommon-dev libx11-xcb-dev xvfb x11-utils zlib1g-dev libfreetype-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm
# Line tables are all a sanitizer report needs. configure and
# make take these in place of DEBUG=1's -O0 -g, adding their own.
CFLAGS: -O0 -g1
CXXFLAGS: -O0 -g1
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Configure
# Non-Qt, as the harness requires (with Qt, main() lives in
# ui_qt.o). Menu enabled: the menu lanes are half the point.
+57
View File
@@ -0,0 +1,57 @@
name: CI QNX input drivers (stand-in SDK)
on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-qnx-standin.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-qnx-standin.yml'
# ci_paths: end
workflow_dispatch:
permissions:
contents: read
env:
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
qnx-standin:
name: QNX input drivers compile against stand-ins for the SDK
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Compile qnx_input.c and qnx_joypad.c
shell: bash
run: |
set -eu
# The BlackBerry 10 SDK cannot be fetched here, so nothing
# built the QNX port and its input driver stopped compiling
# unseen. This compiles the two input files - declarations
# and syntax, nothing linked or run - against stand-ins for
# the SDK's headers. See the script for what that does and
# does not catch.
tools/qnx_standin_check.sh
+39 -34
View File
@@ -4,9 +4,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-qt-companion.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-qt-companion.yml'
# ci_paths: end
permissions:
contents: read
@@ -32,44 +44,37 @@ jobs:
# core's shader accessors are compiled out). That is deliberate - it
# is the one configuration the other jobs never build.
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential pkg-config zlib1g-dev qt6-base-dev libgl-dev
timeout-minutes: 30
steps:
- name: Dependencies
timeout-minutes: 20
run: |
pkgs="build-essential pkg-config zlib1g-dev qt6-base-dev libgl-dev"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Configure
run: |
./configure --enable-qt --disable-x11 --disable-wayland \
+566 -86
View File
@@ -4,9 +4,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-samples-audio.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-samples-audio.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -23,49 +35,41 @@ jobs:
samples-audio:
name: Build and run samples/audio
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential libasound2-dev libsdl2-dev libopenal-dev libpipewire-0.3-dev libpulse-dev pulseaudio ffmpeg gcc-mingw-w64-x86-64 wine64 ccache
timeout-minutes: 25
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential libasound2-dev libsdl2-dev libopenal-dev"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache@v4
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
@@ -75,11 +79,10 @@ jobs:
- name: Enable ccache
run: |
set -eu
command -v ccache >/dev/null 2>&1 || sudo apt-get install -y -qq ccache
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 500M
ccache -M 250M
ccache -z
- name: Build and run mixer_lock_test (ASan + UBSan, TSan)
@@ -133,6 +136,21 @@ jobs:
TSAN_OPTIONS=halt_on_error=1 timeout 600 ./thread_handshake_test
echo "[pass] thread_handshake_test (TSan)"
- name: Build and run mmdevice_active_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/audio/mmdevice_active
run: |
set -eu
# The open-endpoint table the WASAPI notifications consult,
# set from two threads and matched from a third, none of
# which has touched it before.
make clean && make -j$(nproc) all SANITIZER=address,undefined
timeout 300 ./mmdevice_active_test
echo "[pass] mmdevice_active_test (ASan)"
make clean && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./mmdevice_active_test
echo "[pass] mmdevice_active_test (TSan)"
- name: Build and run graph_clock_test (ASan + UBSan)
shell: bash
working-directory: samples/audio/graph_clock
@@ -242,6 +260,24 @@ jobs:
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./alsa_lifecycle_test
echo "[pass] alsa_lifecycle_test (TSan)"
- name: Build and run alsa_chmap_test (ASan + UBSan)
shell: bash
working-directory: samples/audio/alsa_chmap
run: |
set -eu
# The shipping ALSA driver against the null PCM, with the
# channel map, the channel counts the card takes and writei
# scripted via -Wl,--wrap: a card is handed the frontend's
# channels in its own order - its map's, or ALSA's FL FR RL
# RR FC LFE SL SR without one - by write() and through a lent
# mmap span, reports the layout it has, and steps 7.1 down to
# 5.1 or quad before stereo. A short writei must not lose the
# frame stride.
make clean && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
timeout 120 ./alsa_chmap_test
echo "[pass] alsa_chmap_test (ASan)"
- name: Build and run pipeline_stall_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/audio/pipeline_stall
@@ -289,7 +325,7 @@ jobs:
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./upmix_test
echo "[pass] upmix_test (ASan)"
- name: Build and run discrete_multichannel_test (plain, ASan + UBSan)
- name: Build and run discrete_multichannel_test (plain, ASan + UBSan, TSan)
shell: bash
working-directory: samples/audio/discrete_multichannel
run: |
@@ -305,12 +341,26 @@ jobs:
# 5.1 core reaches a Dolby Digital decoder as AC-3 bursts
# that decode back to its own channels. Includes the
# shipping audio_driver.c.
# The builds in turn, the runs at once: each run is one
# thread, so a runner's second core takes another.
# Under ThreadSanitizer only the lane with a core submitting
# from a thread of its own beside the main thread's frame
# ends, pauses and fast-forward changes: the fast-forward
# measurement is the submitter's, and no other thread may
# write it.
. "$GITHUB_WORKSPACE/.github/workflows/scripts/run-parallel.sh"
make clean && make -j$(nproc) all
./discrete_multichannel_test
echo "[pass] discrete_multichannel_test"
mv discrete_multichannel_test discrete_multichannel_test_plain
make clean && make -j$(nproc) all SANITIZER=thread
mv discrete_multichannel_test discrete_multichannel_test_tsan
make clean && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./discrete_multichannel_test
echo "[pass] discrete_multichannel_test (ASan)"
par "discrete_multichannel_test" ./discrete_multichannel_test_plain
par "discrete_multichannel_test (ASan)" env ASAN_OPTIONS=detect_leaks=0 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./discrete_multichannel_test
par "discrete_multichannel_test (TSan: core thread)" env DM_ONLY=inlinethread \
TSAN_OPTIONS=halt_on_error=1 ./discrete_multichannel_test_tsan
par_wait
rm -f discrete_multichannel_test_plain discrete_multichannel_test_tsan
- name: Build and run embedded_alsa_test (the embedded ALSA driver, mock kernel device)
shell: bash
@@ -357,8 +407,6 @@ jobs:
working-directory: samples/audio/bitstream
run: |
set -eu
sudo apt-get update -qq
sudo apt-get install -y -qq ffmpeg
# audio/audio_bitstream.c: a coded stream on its way to a
# receiver, which the PCM pipeline has no place for. Real
# DTS and AC-3 from ffmpeg, handed over frame by frame:
@@ -415,22 +463,201 @@ jobs:
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./qsa_test
echo "[pass] qsa_test (ASan)"
- name: Build and run ctr_audio_test (the 3DS CSND and NDSP drivers against a scripted device)
shell: bash
working-directory: samples/audio/ctr_audio
run: |
set -eu
# audio/drivers/ctr_csnd_audio.c and ctr_dsp_audio.c against a
# stand-in <3ds.h> whose system tick and channel position are
# the device: buffer_size() and write_avail() in bytes of what
# write() takes, and a write longer than the ring taking half
# of it - ASan is what sees a copy run past the ring.
make clean && make -j$(nproc) all
./ctr_audio_test
make clean && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./ctr_audio_test
echo "[pass] ctr_audio_test (ASan)"
- name: Build and run switch_audio_test (the Switch audout driver against a scripted service)
shell: bash
working-directory: samples/audio/switch_audio
run: |
set -eu
# audio/drivers/switch_audio.c's libnx build against a
# stand-in <switch.h> whose audout service queues buffers and
# plays them out on the test's word: write_avail() must be
# what a write takes, across the released buffers, and
# buffer_size() every buffer carrying its share of the
# latency setting.
make clean && make -j$(nproc) all
./switch_audio_test
make clean && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./switch_audio_test
echo "[pass] switch_audio_test (ASan)"
- name: Build and run switch_audren_test (the Switch audren driver's renderer ownership, ASan + UBSan, TSan)
shell: bash
working-directory: samples/audio/switch_audren
run: |
set -eu
# audio/drivers/switch_libnx_audren_audio.c's libnx build
# against a stand-in <switch.h> whose renderer thread plays a
# wave buffer a frame. libnx's audrv is not thread-safe and
# the driver takes no lock: every renderer call must come from
# the writer's thread while another reads write_avail(), a
# write_avail() on the writer's own thread must see what the
# renderer played, non-blocking writes must keep taking audio,
# and stop and start must pause and resume the voice. TSan
# reports two threads in the renderer as a race on the
# stand-in's wave buffer list.
make clean && make -j$(nproc) all
./switch_audren_test
make clean && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./switch_audren_test
echo "[pass] switch_audren_test (ASan)"
make clean && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 ./switch_audren_test
echo "[pass] switch_audren_test (TSan)"
- name: Build and run wiiu_ax_test (the Wii U AX driver's callback against its writer)
shell: bash
working-directory: samples/audio/wiiu_ax
run: |
set -eu
# audio/drivers/wiiu_audio.c against the tree's own Wii U
# headers, with stand-in AX and OS functions behind them: the
# frame callback on one thread and the writer on another keep
# the count of buffered frames in one atomic word, and every
# frame accepted must be buffered or counted played. TSan is
# what sees a field the two share without ordering.
make clean && make -j$(nproc) all
./wiiu_ax_test
make clean && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./wiiu_ax_test
echo "[pass] wiiu_ax_test (ASan)"
make clean && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./wiiu_ax_test
echo "[pass] wiiu_ax_test (TSan)"
make clean
- name: Build and run xenon360_test (the libxenon driver against a scripted sound queue)
shell: bash
working-directory: samples/audio/xenon360
run: |
set -eu
# audio/drivers/xenon360_audio.c against stand-in libxenon
# headers: buffer_size() is the latency setting, write_avail()
# what a write takes, the queue never past it, and a write
# longer than the staging buffer goes in passes - ASan is what
# sees a pass overrun it.
make clean && make -j$(nproc) all
./xenon360_test
make clean && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./xenon360_test
echo "[pass] xenon360_test (ASan)"
- name: Build and run audioworklet_test (the web driver's write against its own ring)
shell: bash
working-directory: samples/audio/audioworklet
run: |
set -eu
# audio/drivers/audioworklet.c, #included by the test and
# built against the tree's emscripten stand-ins: write()
# returns the bytes it took, the ring holds exactly that, and
# frames a write gives up on are counted, not logged.
make clean && make -j$(nproc) all
./audioworklet_test
make clean && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./audioworklet_test
echo "[pass] audioworklet_test (ASan)"
- name: Build and run rwebaudio_test (the web driver's channels, C and JavaScript halves)
shell: bash
working-directory: samples/audio/rwebaudio
run: |
set -eu
# audio/drivers/rwebaudio.c against a stand-in for its
# JavaScript half: frames of the layout the page took go out
# one plane per channel, and every byte count is in frames of
# that layout. Then os/emscripten/library_rwebaudio.js itself
# under Node with a mock AudioContext: the destination is
# widened only past stereo and only as far as its
# maxChannelCount allows, and a stereo one is left as it was.
make clean && make -j$(nproc) check
make clean && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./rwebaudio_test
echo "[pass] rwebaudio_test (ASan)"
- name: Build and run pipewire_write_test (the PipeWire driver's write against its own ring)
shell: bash
working-directory: samples/audio/pipewire_write
run: |
set -eu
# audio/drivers/pipewire.c, #included by the test and built
# against the system's PipeWire headers, with the graph as a
# loop wait that plays a quantum: a write longer than the
# ring's mark takes what fits, or blocking goes in parts, in
# whole frames, and the write path logs nothing.
make clean && make -j$(nproc) all
./pipewire_write_test
make clean && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./pipewire_write_test
echo "[pass] pipewire_write_test (ASan)"
- name: Build and run wasapi_mic_test (the WASAPI microphone's fetch against a scripted capture client)
shell: bash
working-directory: samples/audio/wasapi_mic
run: |
set -eu
# audio/drivers/wasapi.c's text against the shared fake WASAPI
# with a capture client added: a shared-mode FIFO set shorter
# than one engine packet still takes every packet, and a
# packet with no room waits for the FIFO to be read.
make clean && make -j$(nproc) all
./wasapi_mic_test
make clean && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./wasapi_mic_test
echo "[pass] wasapi_mic_test (ASan)"
- name: Build and run oss_report_test (the OSS driver's reports on a device that cannot answer)
shell: bash
working-directory: samples/audio/oss_report
run: |
set -eu
# audio/drivers/oss.c, #included by the test, over Linux's
# soundcard.h: a device that has gone away reports no room and
# no size, and a second of frames asking logs nothing.
make clean && make -j$(nproc) all
./oss_report_test
make clean && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./oss_report_test
echo "[pass] oss_report_test (ASan)"
# RetroFW's jz4760 driver plays whole 16-byte DMA units only.
make clean && make -j$(nproc) all RETROFW=1 SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./oss_report_test
echo "[pass] oss_report_test (RetroFW, ASan)"
- name: Build and run live_drivers_test (a live PulseAudio server, a null ALSA PCM)
shell: bash
working-directory: samples/audio/live_drivers
run: |
set -eu
sudo apt-get update -qq
sudo apt-get install -y -qq libasound2-dev libpulse-dev pulseaudio
# A server with a null sink, and an ALSA config naming a null
# PCM: the real drivers against the real libraries, no
# hardware. Pulse's telemetry is cached from the server
# thread's callbacks; ALSA is run once as the device reports
# thread's callbacks and its writer takes no mainloop lock;
# ALSA is run once as the device reports
# itself and once told it cannot pause, the fallback from
# snd_pcm_pause to snd_pcm_drop.
# snd_pcm_pause to snd_pcm_drop. With the threaded pipeline a
# plug PCM opens for mmap and lends its buffer; a plug over a
# file plugin reads the lent bytes back.
export XDG_RUNTIME_DIR=/tmp/xdg
mkdir -p "$XDG_RUNTIME_DIR/pulse"
printf 'pcm.nullpcm { type null }\n' > "$HOME/.asoundrc"
printf '%s\n' 'pcm.nullpcm { type null }' \
'pcm.plugnull { type plug slave.pcm "nullpcm" }' \
'pcm.filenull { type file slave.pcm "nullpcm" file "/tmp/ra_live_drivers_lend.raw" format raw }' \
'pcm.plugfile { type plug slave.pcm "filenull" }' > "$HOME/.asoundrc"
pulseaudio --daemonize=no --exit-idle-time=-1 --disallow-exit -n \
-L "module-null-sink sink_name=nullsink rate=48000 channels=2" \
-L "module-native-protocol-unix auth-anonymous=1 socket=$XDG_RUNTIME_DIR/pulse/native" \
@@ -443,6 +670,11 @@ jobs:
make clean && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./live_drivers_test
echo "[pass] live_drivers_test (ASan)"
# Pulse's writer hands audio to the server's thread through a
# ring and an eventcount, which is what TSan is for.
make clean && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1:suppressions=$PWD/tsan.supp ./live_drivers_test
echo "[pass] live_drivers_test (TSan)"
kill $PA_PID || true
- name: Build and run binaural_test (plain, ASan + UBSan)
@@ -482,38 +714,39 @@ jobs:
# ratio at a bound and the pipe overflows behind it. Real time,
# so a loaded runner can shift the mean; the tolerance is ten
# times the settled residual.
make clean && make -j$(nproc) all SANITIZER=address,undefined
test -x pipeline_rate_control_test
ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1 timeout 120 \
./pipeline_rate_control_test
echo "[pass] pipeline_rate_control_test (ASan)"
# A core late by 60 ms every 2 s, audio sync off: the silence is
# the stalls' own and no more, the late audio is not kept, and
# rate control is not pinned against a backlog. And with sync on:
# the writer waits, nothing is dropped.
ASAN_OPTIONS=detect_leaks=0 timeout 120 ./pipeline_rate_control_test 12 4 jitter
ASAN_OPTIONS=detect_leaks=0 timeout 120 ./pipeline_rate_control_test 12 4 sync-jitter
echo "[pass] pipeline_rate_control_test (late core, ASan)"
# The 64 ms default buffer, sync off, a steady core and a late one:
# the consumer's chunks are whole publishes and the pipe's fill
# swings by one with the phase; nothing is dropped from a steady
# core at any phase.
ASAN_OPTIONS=detect_leaks=0 timeout 120 ./pipeline_rate_control_test 16 4 big
ASAN_OPTIONS=detect_leaks=0 timeout 120 ./pipeline_rate_control_test 16 4 big-jitter
echo "[pass] pipeline_rate_control_test (64 ms device, ASan)"
# The producer held 4 ms every five seconds, as a loaded runner
# holds it: a publish pushed across a sink window boundary. The
# estimate must settle and apply about zero, not the shift.
ASAN_OPTIONS=detect_leaks=0 timeout 120 ./pipeline_rate_control_test 16 4 big-stall
echo "[pass] pipeline_rate_control_test (stalled producer, ASan)"
# A float core on a float ring into a float device: a signal a
# tenth of an int16 LSB high arrives as published, not as zero.
ASAN_OPTIONS=detect_leaks=0 timeout 120 ./pipeline_rate_control_test 16 4 float
echo "[pass] pipeline_rate_control_test (float core, ASan)"
# Every run is seconds of audio against a device that drains
# in real time, on almost no CPU: all eight at once, so the
# step takes as long as the longest of them.
. "$GITHUB_WORKSPACE/.github/workflows/scripts/run-parallel.sh"
make clean && make -j$(nproc) all SANITIZER=address,undefined
mv pipeline_rate_control_test pipeline_rate_control_test_asan
make clean && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 240 ./pipeline_rate_control_test
echo "[pass] pipeline_rate_control_test (TSan)"
test -x pipeline_rate_control_test_asan && test -x pipeline_rate_control_test
A="env ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1 timeout 120 ./pipeline_rate_control_test_asan"
par "pipeline_rate_control_test (ASan)" $A
par "pipeline_rate_control_test (late core, ASan)" $A 12 4 jitter
par "pipeline_rate_control_test (late core, Audio Sync, ASan)" $A 12 4 sync-jitter
par "pipeline_rate_control_test (64 ms device, ASan)" $A 16 4 big
par "pipeline_rate_control_test (64 ms device, late core, ASan)" $A 16 4 big-jitter
par "pipeline_rate_control_test (stalled producer, ASan)" $A 16 4 big-stall
par "pipeline_rate_control_test (float core, ASan)" $A 16 4 float
par "pipeline_rate_control_test (TSan)" env TSAN_OPTIONS=halt_on_error=1 \
timeout 240 ./pipeline_rate_control_test
par_wait
rm -f pipeline_rate_control_test_asan
- name: Build and run resampler_reset_test (ASan + UBSan)
shell: bash
working-directory: samples/audio/resampler_reset
@@ -703,6 +936,30 @@ jobs:
./coreaudio_ring_test
echo "[pass] coreaudio_ring_test (ASan)"
- name: Build and run sdl_lost_wakeup_test (the SDL2 driver against SDL's dummy device)
shell: bash
working-directory: samples/audio/sdl_lost_wakeup
run: |
set -eu
# The producer's waits and the capture parks are measured; the
# lend pair is asserted: whole-frame spans, published to a
# device that pulls them, refused while stopped.
make clean && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
SDL_AUDIODRIVER=dummy timeout 300 ./sdl_lost_wakeup_test 1
echo "[pass] sdl_lost_wakeup_test (ASan)"
- name: Build and run coreaudio_write_test (the shipping writer, lend included, on stubs)
shell: bash
working-directory: samples/audio/coreaudio_write
run: |
set -eu
# run.py lifts the writer, the render callback and the lend
# pair out of audio/drivers/coreaudio.c and drives them on a
# simulated device clock; no Apple framework is involved.
python3 run.py
echo "[pass] coreaudio_write_test"
- name: Build and run asio_clock_test (ASan + UBSan)
shell: bash
working-directory: samples/audio/asio_clock
@@ -755,11 +1012,36 @@ jobs:
./asio_ring_test
echo "[pass] asio_ring_test (ASan)"
- name: Build and run asio_teardown_test (TSan, ASan, MinGW under wine)
shell: bash
working-directory: samples/audio/asio_teardown
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
# The ASIO teardown's wait for a callback still inside after
# ASIOStop, the driver's code pulled out of asio.c: it ends as
# the last callback leaves, never while one is inside, and at
# 200 ms for one that never leaves. A build whose last callback
# out does not notify must fail.
for san in thread address,undefined; do
make clean && make -j$(nproc) all SANITIZER=$san
timeout 60 ./asio_teardown_test
done
make clean && make -j$(nproc) all TEST_DEFINES=-DASIO_TEST_NO_OUT_NOTIFY
if timeout 60 ./asio_teardown_test; then
echo "negative build passed"; exit 1
fi
make clean && make -j$(nproc) all CC=x86_64-w64-mingw32-gcc LDFLAGS=-static
WINEDEBUG=-all timeout 120 make check CC=x86_64-w64-mingw32-gcc LDFLAGS=-static
make clean
echo "[pass] asio_teardown_test"
- name: Build and run dsound_pump_test (MinGW, under wine)
shell: bash
working-directory: samples/audio/dsound_pump
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
# audio/drivers/dsound.c end to end on wine's DirectSound, fed
# by an ALSA null PCM, which plays at real-time pace on a
# runner with no sound card. Pins that the pump - which now
@@ -768,13 +1050,8 @@ jobs:
# millisecond - still moves audio through blocking writes,
# fills silence when the writer stops, and lets free() return
# at once while it waits.
if ! command -v x86_64-w64-mingw32-gcc >/dev/null 2>&1 \
|| ! command -v wine >/dev/null 2>&1; then
sudo apt-get install -y -qq gcc-mingw-w64-x86-64 wine64 \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30
fi
printf 'pcm.!default { type null }\nctl.!default { type hw card 0 }\n' > ~/.asoundrc
make clean all
make clean && make -j$(nproc) all
WINEDEBUG=-all timeout 300 make check
echo "[pass] dsound_pump_test (wine)"
@@ -783,6 +1060,7 @@ jobs:
working-directory: samples/audio/wdmks_loop
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
# audio/drivers/wdmks.c compiled whole, its WaveRT loop driven
# by hand: the test owns the driver's state, points the
# position register at a word of its own and moves the
@@ -799,15 +1077,41 @@ jobs:
# milliseconds where it reports none; and that gaps in the
# audio are counted once each, neither a start nor every
# sample of a pause.
if ! command -v x86_64-w64-mingw32-gcc >/dev/null 2>&1 \
|| ! command -v wine >/dev/null 2>&1; then
sudo apt-get install -y -qq gcc-mingw-w64-x86-64 wine64 \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30
fi
make clean all
make clean && make -j$(nproc) all
WINEDEBUG=-all timeout 120 make check
echo "[pass] wdmks_loop_test (wine)"
- name: Check MMDevice notification targets on both thread backends
shell: bash
working-directory: samples/audio/mmdevice_notification
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
for threads in 0 1; do
make clean check THREADS=$threads SANITIZER=address,undefined
make clean THREADS=$threads CC=x86_64-w64-mingw32-gcc LDFLAGS=-static && make -j$(nproc) all THREADS=$threads CC=x86_64-w64-mingw32-gcc LDFLAGS=-static
WINEDEBUG=-all wine ./mmdevice_notification_test.exe
done
make clean
- name: Build and run xaudio_lend_test (wine)
shell: bash
working-directory: samples/audio/xaudio_lend
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
# The XAudio2 driver lends the free span at the head of its
# buffers (write_begin / write_end), so the frontend's output
# lands in the buffer XAudio2 plays instead of being copied
# in by write(). On wine's XAudio2: a lend stays within the
# free room and the allocation, lends and writes make one
# stream, write_end publishes all it is given without
# waiting, an abandoned lend takes nothing, and lent audio
# plays at the pace written audio does.
make clean && make -j$(nproc) all
REQUIRE_XAUDIO=1 WINEDEBUG=-all timeout 120 make check
echo "[pass] xaudio_lend_test (wine)"
- name: Build and run wasapi_exclusive_pacing_test (ASan + UBSan, then TSan)
shell: bash
working-directory: samples/audio/wasapi_exclusive_pacing
@@ -823,14 +1127,24 @@ jobs:
# thread and the drain-first shared write this run gave 3-10% of
# periods unanswered; the first smaller-period attempt without the
# pump gave 75%.
# Each scenario is seconds of a fake device on the wall clock,
# on next to no CPU: every scenario under both sanitizers at
# once, so the step takes as long as one scenario.
. "$GITHUB_WORKSPACE/.github/workflows/scripts/run-parallel.sh"
make clean && make -j$(nproc) all SANITIZER=address,undefined
test -x wasapi_exclusive_pacing_test
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 timeout 120 \
./wasapi_exclusive_pacing_test 3
echo "[pass] wasapi_exclusive_pacing_test (ASan)"
mv wasapi_exclusive_pacing_test wasapi_exclusive_pacing_test_asan
make clean && make -j$(nproc) all SANITIZER=thread
timeout 180 ./wasapi_exclusive_pacing_test 3
echo "[pass] wasapi_exclusive_pacing_test (TSan)"
test -x wasapi_exclusive_pacing_test_asan && test -x wasapi_exclusive_pacing_test
n=$(PACING_SCENARIO=count ./wasapi_exclusive_pacing_test_asan)
for sc in $(seq 0 $((n - 1))) rest; do
par "wasapi_exclusive_pacing_test $sc (ASan)" env PACING_SCENARIO=$sc \
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 \
timeout 120 ./wasapi_exclusive_pacing_test_asan 3
par "wasapi_exclusive_pacing_test $sc (TSan)" env PACING_SCENARIO=$sc \
timeout 180 ./wasapi_exclusive_pacing_test 3
done
par_wait
rm -f wasapi_exclusive_pacing_test_asan
- name: Build and run sink_rate_test (ASan + UBSan)
shell: bash
@@ -945,6 +1259,108 @@ jobs:
echo "AVX unavailable on this runner; scalar/SSE tests ran above."
fi
- name: Build and run sinc kernel dispatch test (C89, ASan + UBSan)
working-directory: samples/audio/sinc_dispatch
run: |
# A baseline build carries the AVX and AVX-512 kernels through
# per-function targets and runs each one the runner supports
# against the scalar kernel; the -mavx build must agree.
make -j$(nproc) check SANITIZER=address,undefined
if grep -qw avx /proc/cpuinfo; then
make clean
make -j$(nproc) check CC="gcc -mavx" SANITIZER=address,undefined
fi
- name: Build and run sinc fast-forward test (C89, ASan + UBSan, MinGW)
working-directory: samples/audio/sinc_fastforward
run: |
# Live ratios under the table's design are decimated first:
# rejection as a table built for that ratio, the nominal path
# untouched, the output bound, crossfaded stage changes.
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
. "$GITHUB_WORKSPACE/.github/workflows/scripts/run-parallel.sh"
# Each build its own binary, then the runs at once: each is one
# thread, so the runner's cores take them side by side.
make clean
make TARGET=sff_asan SANITIZER=address,undefined
par "sinc_fastforward_test (ASan)" ./sff_asan
if grep -qw avx /proc/cpuinfo; then
make TARGET=sff_avx CC="gcc -mavx" SANITIZER=address,undefined
par "sinc_fastforward_test (AVX, ASan)" ./sff_avx
fi
make TARGET=sff_mingw CC=x86_64-w64-mingw32-gcc
par "sinc_fastforward_test (MinGW, wine)" env WINEDEBUG=-all \
timeout 300 wine ./sff_mingw.exe
par_wait
rm -f sff_asan sff_avx sff_mingw.exe
- name: Build and run sinc sibling test (C89, ASan + UBSan, MinGW)
working-directory: samples/audio/sinc_sibling
run: |
# Siblings share their parent's tables: a fresh stream either
# way, own state only, either free order.
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
. "$GITHUB_WORKSPACE/.github/workflows/scripts/run-parallel.sh"
make clean
make TARGET=sibling_asan SANITIZER=address,undefined
make TARGET=sibling_mingw CC=x86_64-w64-mingw32-gcc
par "sinc_sibling_test (ASan)" ./sibling_asan
par "sinc_sibling_test (MinGW, wine)" env WINEDEBUG=-all \
timeout 300 wine ./sibling_mingw.exe
par_wait
rm -f sibling_asan sibling_mingw.exe
- name: Build and run mic_handshake_test (ASan + UBSan)
working-directory: samples/audio/mic_handshake
run: |
# The shipping capture worker against a clocked fake device, and
# only the resampler the microphone flush uses being built.
make clean
make -j$(nproc) SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
timeout 300 ./mic_handshake_test 1
- name: Build and run pipeline_format_test (ASan + UBSan)
working-directory: samples/audio/pipeline_format
run: |
# Real init and one batch through each format and transport;
# the integer front pair exists only behind its hint.
make -j$(nproc) check SANITIZER=address,undefined
- name: Build and run pipeline_rate_change_test (ASan + UBSan, mingw)
working-directory: samples/audio/pipeline_rate_change
run: |
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
# A core rate change taken by the running driver: one device
# open, the tone at its pitch on both sides, the ring grown
# where the new rate needs it, time stretch refused.
make -j$(nproc) check SANITIZER=address,undefined
make clean
WINEDEBUG=-all timeout 300 make check CC=x86_64-w64-mingw32-gcc \
LDFLAGS="-lm -static" RUN="wine ./pipeline_rate_change_test.exe"
- name: Build and run unity_identity_test (plain, ASan + UBSan)
working-directory: samples/audio/unity_identity
run: |
# The unity passthrough end to end: an s16 batch at a pinned
# ratio of 1.0 reaches an s16 device with zero conversions,
# counted through linker wraps; the fallbacks still convert.
make -j$(nproc) check
make clean
make -j$(nproc) check SANITIZER=address,undefined
- name: Build and run lend_write_test (plain, ASan + UBSan)
working-directory: samples/audio/lend_write
run: |
# The driver lend pair: the pipeline produces its output in
# the driver's own buffer, bit-identical to the written
# stream, with write() never called while a lend is live.
make -j$(nproc) check
make clean
make -j$(nproc) check SANITIZER=address,undefined
- name: Build and run extra-channel capacity test (ASan + UBSan)
working-directory: samples/audio/extra_capacity
run: |
@@ -952,7 +1368,71 @@ jobs:
make clean
make -j$(nproc) check SIMD_MASK=RESAMPLER_SIMD_SSE SANITIZER=address,undefined
- name: Build and run the pipeline fill cap lane (plain)
shell: bash
working-directory: samples/audio/pipeline_clocked
run: |
set -eu
# The threaded pipeline against a device that drains on a
# clock, with Audio Sync the only thing pacing the core: the
# ring sits full and every publish waits behind it. With
# audio_pipeline_fill_cap the queue has to come down to the
# device's buffer and no more than a publish past it, where a
# blocking inline write leaves it, without the device running
# dry. With Audio Sync off it has to take the queue down too,
# with a core up to a frame late not reaching the device as
# silence. And display-paced with Audio Sync on, rate control
# has to hold the queue where the frame-synchronous path
# holds it. With audio the only clock, rate control has to
# leave the ratio alone, inline and threaded, and still hold
# the fill when a present blocks behind the runloop's back.
# And neither leaving fast-forward nor resuming from a pause
# may hold the core while a queue plays out, threaded or
# inline (#19940).
# Plain, not sanitized: it is the clock that is measured,
# and a sanitizer's slowdown is a host that starves the
# consumer whatever the cap does. Underruns with the cap are
# retried, since a host that takes the consumer off its core
# shows the same with the cap off; only a cap that never runs
# clean fails.
# Its lanes run in real time on next to no CPU, so they run
# at once, one setting each: the step takes as long as the
# longest setting, about 12 s, not the longest case.
. "$GITHUB_WORKSPACE/.github/workflows/scripts/run-parallel.sh"
make clean && make -j$(nproc)
test -x pipeline_clocked_test
for lane in audio_paced_16 audio_paced_32 audio_paced_64 \
setpoint_16 setpoint_32 setpoint_64 \
rate_control_32 rate_control_64 sync_off \
ff_exit_1 ff_exit_2 ff_exit_3 ff_exit_4 ff_exit_5; do
par "pipeline_clocked_test (fill cap: $lane)" \
env PACED_ONLY=$lane timeout 300 ./pipeline_clocked_test 3
done
par_wait
- name: Build and run resampler option tests (C89, ASan + UBSan)
working-directory: samples/audio/resampler_options
run: |
make -j$(nproc) check SANITIZER=address,undefined
- name: Build and run CoreMIDI input queue test (ASan + UBSan, TSan)
working-directory: samples/midi/coremidi_queue
run: |
# midi/drivers/coremidi_queue.h is the macOS driver's lock-free
# input queue; it builds and runs anywhere.
make check SANITIZER=address,undefined
make clean
make check SANITIZER=thread
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+169 -58
View File
@@ -9,6 +9,8 @@ on:
- 'libretro-common/queues/**'
- 'libretro-common/include/retro_spsc.h'
- 'libretro-common/include/retro_atomic.h'
- 'camera/drivers/**'
- 'samples/camera/**'
- 'samples/cores/**'
- '.github/workflows/Linux-samples-cores.yml'
pull_request:
@@ -19,6 +21,8 @@ on:
- 'libretro-common/queues/**'
- 'libretro-common/include/retro_spsc.h'
- 'libretro-common/include/retro_atomic.h'
- 'camera/drivers/**'
- 'samples/camera/**'
- 'samples/cores/**'
- '.github/workflows/Linux-samples-cores.yml'
workflow_dispatch:
@@ -37,49 +41,53 @@ jobs:
samples-cores:
name: Build and run samples/cores
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential pkg-config ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libv4l-dev libasound2-dev ccache libavdevice-dev libavfilter-dev libpipewire-0.3-dev mesa-vulkan-drivers
timeout-minutes: 25
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential pkg-config ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Select software Vulkan for CPU fixtures
run: |
set -eu
for icd in /usr/share/vulkan/icd.d/lvp_icd*.json; do
if [ -f "$icd" ]; then
printf 'VK_ICD_FILENAMES=%s\n' "$icd" >> "$GITHUB_ENV"
exit 0
fi
done
echo "Lavapipe ICD is missing" >&2
exit 1
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache@v4
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
@@ -89,11 +97,10 @@ jobs:
- name: Enable ccache
run: |
set -eu
command -v ccache >/dev/null 2>&1 || sudo apt-get install -y -qq ccache
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 500M
ccache -M 250M
ccache -z
- name: Build and run camera ffmpeg_live_test (plain, ASan + UBSan, TSan)
@@ -101,32 +108,73 @@ jobs:
working-directory: samples/camera/ffmpeg_live
run: |
set -eu
sudo apt-get update -qq
sudo apt-get install -y -qq libavdevice-dev libavfilter-dev
# The camera driver against lavfi: built with its input
# format set to libavfilter, a filter graph is the device and
# the whole pipeline runs with no camera attached - avformat
# opens the source, the poll thread reads, the decoder
# decodes, swscale converts and poll() takes the frame out
# under the buffer's lock.
# decodes, swscale converts and poll() takes the newest
# finished frame.
#
# The cases are the ones the threading has to get right and
# that no eyeball on a webcam would show: start and stop
# repeatedly, stop while the thread is mid-frame, free
# without stopping, poll before starting, and a source that
# does not exist.
make clean all
# without stopping, poll before starting, a source that
# does not exist, a poll that must show the last frame
# decoded, and a slow poll the thread must not wait on. Leak
# checking is on: a packet or plane kept per frame or per
# start is a leak report.
make clean && make -j$(nproc) all
./ffmpeg_live_test
echo "[pass] ffmpeg_live_test"
make clean all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
timeout 600 ./ffmpeg_live_test
echo "[pass] ffmpeg_live_test (ASan)"
make clean all SANITIZER=thread
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1:suppressions=../../cores/ffmpeg_audio_pipe/tsan.supp \
timeout 600 ./ffmpeg_live_test
echo "[pass] ffmpeg_live_test (TSan)"
- name: Build and run camera pipewire_poll_test (plain, ASan + UBSan)
shell: bash
working-directory: samples/camera/pipewire_poll
run: |
set -eu
# The pipewire camera driver's poll against a scripted
# stream, the pw_stream calls link-wrapped: an empty queue
# returns at once, and every buffer the poll takes - valid,
# corrupted or without CPU data - is queued back. The check
# target bounds the run: a poll that waits for a frame is
# the failure.
make clean check
echo "[pass] pipewire_poll_test"
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
timeout 60 ./pipewire_poll_test
echo "[pass] pipewire_poll_test (ASan)"
- name: FFmpeg hardware-context replacement (plain, ASan + UBSan)
working-directory: samples/cores/ffmpeg_hw_context
run: |
set -eu
make clean
make check
make clean
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 \
make check SANITIZER=address,undefined
- name: FFmpeg subtitle hand-off to the renderer (plain, ASan + UBSan, TSan)
working-directory: samples/cores/ffmpeg_ass_events
run: |
set -eu
make clean
make check
make clean
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 \
make check SANITIZER=address,undefined
make clean
TSAN_OPTIONS=halt_on_error=1 make check SANITIZER=thread
- name: Build and run ffmpeg_sweep_test (plain, ASan + UBSan, TSan)
shell: bash
working-directory: samples/cores/ffmpeg_sweep
@@ -141,21 +189,48 @@ jobs:
# empty or absent, then teardown while the decode thread is
# still working.
#
# Leak detection off: libavutil's HW-device probing keeps a
# few objects, as the audio-pipe lane already has it. The
# libav* TSan suppressions are the same ones and for the
# same reason.
make clean all
# With leak detection: every load and unload in it, the
# decoder probing included, must give back its codec
# contexts and hardware devices. The libav* TSan
# suppressions are the audio-pipe lane's, for the same
# reason.
make clean && make -j$(nproc) all
./ffmpeg_sweep_test
echo "[pass] ffmpeg_sweep_test"
make clean all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=0:detect_stack_use_after_return=1 \
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 900 ./ffmpeg_sweep_test
echo "[pass] ffmpeg_sweep_test (ASan)"
make clean all SANITIZER=thread
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1:suppressions=../ffmpeg_audio_pipe/tsan.supp \
timeout 900 ./ffmpeg_sweep_test
echo "[pass] ffmpeg_sweep_test (TSan)"
# The standalone core's own makefiles: a link with
# --no-undefined is the check their source lists are whole,
# and a dry run with GL (no GL headers here) the check that
# the GL build's list resolves. The sample's objects share
# paths with the core's libretro-common ones: cleaned first.
make clean
make -n -C ../../../cores/libretro-ffmpeg OPENGL=1 >/dev/null
make -C ../../../cores/libretro-ffmpeg -j2 OPENGL=0 HAVE_SSA=0
make -C ../../../cores/libretro-ffmpeg clean OPENGL=0 HAVE_SSA=0
echo "[pass] standalone ffmpeg core links"
- name: Build and run ffmpeg_finished_wait_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/cores/ffmpeg_finished_wait
run: |
set -eu
# The main thread's wait for a finished video slot ends when
# the decode thread does, at the end of a clip, instead of
# holding retro_run() on a slot nothing will finish.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1 \
timeout 60 ./ffmpeg_finished_wait_test
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1:suppressions=../ffmpeg_audio_pipe/tsan.supp \
timeout 60 ./ffmpeg_finished_wait_test
echo "[pass] ffmpeg_finished_wait_test"
- name: Build and run ffmpeg_audio_pipe_test (ASan + UBSan, TSan)
shell: bash
@@ -170,13 +245,13 @@ jobs:
# teardown mid-stream, all on a watchdog. Needs the ffmpeg
# CLI to generate the clip. Leak detection off: libavutil's
# own HW-device probing allocates and keeps a few objects.
make clean all SANITIZER=address,undefined
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x ffmpeg_audio_pipe_test
ASAN_OPTIONS=detect_leaks=0:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 300 \
./ffmpeg_audio_pipe_test
echo "[pass] ffmpeg_audio_pipe_test (ASan)"
make clean all SANITIZER=thread
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
# The libav* libraries are uninstrumented, so TSan cannot see
# the atomics that order an AVBufferRef's reference count and
# reports the allocation and the release as a race. Only
@@ -185,3 +260,39 @@ jobs:
TSAN_OPTIONS=halt_on_error=1:suppressions=tsan.supp \
timeout 300 ./ffmpeg_audio_pipe_test
echo "[pass] ffmpeg_audio_pipe_test (TSan)"
- name: Build and run videoproc_audio_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/cores/videoproc_audio
run: |
set -eu
# cores/libretro-video-processor's ALSA capture handle between
# the frontend's audio thread, which keeps calling the core's
# audio callback, and the main thread opening and closing the
# device under it: no read on a closed handle - a close that
# lands mid-read included - and no lock taken by the callback.
# The device is ALSA's null PCM; the capture read is a
# stand-in.
make clean && make -j$(nproc) all
timeout 120 ./videoproc_audio_test
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
timeout 120 ./videoproc_audio_test
echo "[pass] videoproc_audio_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./videoproc_audio_test
echo "[pass] videoproc_audio_test (TSan)"
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+217 -45
View File
@@ -4,9 +4,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-samples-formats.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-samples-formats.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -20,52 +32,44 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
samples-formats:
name: Build and run samples/formats
formats-core:
name: Core format decoders and containers
runs-on: ubuntu-latest
timeout-minutes: 10
env:
APT_PKGS: build-essential ffmpeg clang ccache
timeout-minutes: 30
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential ffmpeg"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache@v4
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
@@ -75,13 +79,21 @@ jobs:
- name: Enable ccache
run: |
set -eu
command -v ccache >/dev/null 2>&1 || sudo apt-get install -y -qq ccache
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 500M
ccache -M 250M
ccache -z
- name: HEVC hidden pictures and prior-output discard
run: make -C libretro-common/samples/formats/h265 output-flags-check SANITIZER=address,undefined
- name: HEVC parameter replacement and output ownership (TSan)
run: TSAN_OPTIONS=halt_on_error=1 make -C libretro-common/samples/formats/h265 -B output-flags-check SANITIZER=thread
- name: MP4 hidden-picture timestamps, seeks and partial input
run: make -C libretro-common/samples/formats/vp9 mp4-h26x-check SANITIZER=address,undefined
- name: Build and run dts_lpcm_test (plain, ASan + UBSan)
shell: bash
working-directory: samples/formats/dts_lpcm
@@ -112,10 +124,10 @@ jobs:
# decoder is for: every frame repacked to the plain core,
# put in the IEC 61937 burst its sample count calls for, and
# read back out of that burst byte for byte.
make clean all
make clean && make -j$(nproc) all
./dts_lpcm_test
echo "[pass] dts_lpcm_test"
make clean all SANITIZER=address,undefined
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 ./dts_lpcm_test
echo "[pass] dts_lpcm_test (ASan)"
@@ -152,7 +164,7 @@ jobs:
# paths and report a pass. That count is kept per format, not
# just in total, so one decoder quietly ceasing to accept its
# seed cannot hide behind the other three.
make clean all SANITIZER=address,undefined
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x image_decode_fuzz_test
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 \
timeout 120 ./image_decode_fuzz_test
@@ -217,8 +229,168 @@ jobs:
# begin on, it must accept before the whole file has arrived
# (or partial decoding is silently off), and it must be
# monotonic in the prefix length.
make clean all SANITIZER=address,undefined
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x image_avail_test
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 \
timeout 120 ./image_avail_test
echo "[pass] image_avail_test"
- name: AV1 picture tools, frame prefixes, tiles and container packets (ASan + UBSan)
working-directory: libretro-common/samples/formats/av1
run: |
make check SANITIZER=address,undefined
python3 fixtures.py
python3 tile_fixtures.py
python3 frame_fixtures.py
python3 picture_fixtures.py
python3 reference_fixtures.py
python3 grain_fixtures.py
python3 state_fixtures.py
- name: Vulkan AV1 parameters against real streams (ASan + UBSan)
working-directory: samples/gfx/vulkan_video_caps
run: |
make av1_params_test SANITIZER=address,undefined
python3 av1_fixtures.py
- name: Build and run video_hdr_fp16_test (ASan + UBSan + LSan)
shell: bash
working-directory: samples/formats/video_hdr_fp16
run: |
set -eu
# HDR10 video to linear scRGB half floats: HEVC Main10 in MP4
# and VP9 profile 2 in WebM, tagged PQ, decode flat grey onto
# the PQ EOTF when asked into the caller's frame; their
# SDR-tagged twins, and any stream decoding into its own
# canvas, keep the 32-bit paths. Fixtures made with ffmpeg
# (libx265, libvpx-vp9) from known 10-bit grey.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 \
timeout 120 ./video_hdr_fp16_test
- name: Build and run image_tile_gx_test (ASan + UBSan + LSan)
shell: bash
working-directory: samples/formats/image_tile_gx
run: |
set -eu
# The GameCube/Wii GX RGBA8 tiling the gx driver's overlays
# are sampled in, which runs nowhere but on the console. It
# is checked against the layout's definition and against
# the band conversion it replaced, over every size up to
# 70x44 so widths and heights off a multiple of four - where
# the source pitch and the tile width part ways - are all
# covered.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x image_tile_gx_test
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 \
timeout 120 ./image_tile_gx_test
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
vp9-matroska:
name: VP9 and Matroska codec conformance
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential ffmpeg clang ccache
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Enable ccache
run: |
set -eu
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 250M
ccache -z
- name: VP9 sessions and WebM GPU dispatch (ASan + UBSan)
working-directory: libretro-common/samples/formats/vp9
run: |
make check stream-check h26x-check h26x-color-check pixel-check SANITIZER=address,undefined
make stack-check CC=gcc
make stack-check CC=clang
H26X_TEST_THREADS=4 make h26x-check h26x-color-check SANITIZER=address,undefined
- name: Matroska concurrent decode and output ownership (TSan)
working-directory: libretro-common/samples/formats/vp9
run: |
make -B rwebm_h26x_stream_test rwebm_h26x_hw_test SANITIZER=thread
H26X_TEST_THREADS=4 TSAN_OPTIONS=halt_on_error=1 make h26x-check SANITIZER=thread
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
samples-formats:
name: Build and run samples/formats
runs-on: ubuntu-latest
needs: [formats-core, vp9-matroska]
if: always()
timeout-minutes: 1
steps:
- name: Require both format suites
env:
FORMATS_RESULT: ${{ needs.formats-core.result }}
CODECS_RESULT: ${{ needs.vp9-matroska.result }}
run: |
test "$FORMATS_RESULT" = success
test "$CODECS_RESULT" = success
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+123 -57
View File
@@ -4,9 +4,43 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- 'samples/network/cheevos_login/**'
- 'samples/network/gdrive_backup/**'
- 'samples/network/net_http_semantics/**'
- 'samples/network/nfs_backup/**'
- 'samples/network/s3_backup/**'
- 'samples/network/smb_backup/**'
- 'samples/network/webdav_backup/**'
- 'samples/network/webdav_options/**'
- 'samples/network/webdav_reauth/**'
- '.github/workflows/Linux-samples-network.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- 'samples/network/cheevos_login/**'
- 'samples/network/gdrive_backup/**'
- 'samples/network/net_http_semantics/**'
- 'samples/network/nfs_backup/**'
- 'samples/network/s3_backup/**'
- 'samples/network/smb_backup/**'
- 'samples/network/webdav_backup/**'
- 'samples/network/webdav_options/**'
- 'samples/network/webdav_reauth/**'
- '.github/workflows/Linux-samples-network.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -23,49 +57,41 @@ jobs:
samples-network:
name: Build and run samples/network
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential libmbedtls-dev libbearssl-dev ccache
timeout-minutes: 15
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential libmbedtls-dev libbearssl-dev"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache@v4
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
@@ -75,11 +101,10 @@ jobs:
- name: Enable ccache
run: |
set -eu
command -v ccache >/dev/null 2>&1 || sudo apt-get install -y -qq ccache
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 500M
ccache -M 250M
ccache -z
- name: Build and run webdav_options_test (ASan + UBSan, TSan)
@@ -99,13 +124,13 @@ jobs:
# WebDAV methods, HTTP/2 lowercase field names, plain HTTP
# with and without PUT, empty and NULL header lists, and
# whole-token matching (PROPFINDX must not match PROPFIND).
make clean all SANITIZER=address,undefined
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x webdav_options_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./webdav_options_test
echo "[pass] webdav_options_test (ASan)"
make clean all SANITIZER=thread
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./webdav_options_test
echo "[pass] webdav_options_test (TSan)"
@@ -127,13 +152,13 @@ jobs:
# A malformed challenge (a value with no closing quote, a
# trailing unquoted parameter) must be refused, not crash the
# parser or send it reading past the end of the line.
make clean all SANITIZER=address,undefined
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x webdav_reauth_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./webdav_reauth_test
echo "[pass] webdav_reauth_test (ASan)"
make clean all SANITIZER=thread
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./webdav_reauth_test
echo "[pass] webdav_reauth_test (TSan)"
@@ -152,13 +177,13 @@ jobs:
# back up", a failed backup stopping the upload, destructive
# sync and the server manifest skipping the backup, and COPY
# and PUT each getting their own Digest retry.
make clean all SANITIZER=address,undefined
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x webdav_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./webdav_backup_test
echo "[pass] webdav_backup_test (ASan)"
make clean all SANITIZER=thread
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./webdav_backup_test
echo "[pass] webdav_backup_test (TSan)"
@@ -177,13 +202,13 @@ jobs:
# failed copy (including a 200 with an <Error> body) stopping
# the delete or upload, and destructive sync and the server
# manifest skipping the copy.
make clean all SANITIZER=address,undefined
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x s3_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./s3_backup_test
echo "[pass] s3_backup_test (ASan)"
make clean all SANITIZER=thread
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./s3_backup_test
echo "[pass] s3_backup_test (TSan)"
@@ -204,16 +229,43 @@ jobs:
# the old file intact with no temporary file left, no backup
# for destructive sync, a new file or the manifest, an empty
# upload and the configured subdirectory.
make clean all SANITIZER=address,undefined
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x smb_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./smb_backup_test
echo "[pass] smb_backup_test (ASan)"
make clean all SANITIZER=thread
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./smb_backup_test
echo "[pass] smb_backup_test (TSan)"
- name: Build and run nfs_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/nfs_backup
run: |
set -eu
# Includes network/cloud_sync/nfs.c and runs it against an
# in-memory export (fake_nfs.c) keeping net_nfs3.c's status
# contract. A download is written beside the local file and
# renamed over it once complete; only the server answering
# NOENT reads as "not there"; an upload is one RENAME over the
# old file, kept as <path>-<time>[-n] with destructive sync
# off. Covers failed opens and partial reads leaving the local
# save intact, unanswered stat/unlink failing instead of
# reporting a missing file or a delete, failed write, backup
# and final rename leaving the old file and no temporary, the
# manifest skipping the backup, refused over-long names, the
# subdirectory and a concurrent MKDIR.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x nfs_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./nfs_backup_test
echo "[pass] nfs_backup_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./nfs_backup_test
echo "[pass] nfs_backup_test (TSan)"
- name: Build and run gdrive_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/gdrive_backup
@@ -229,13 +281,13 @@ jobs:
# copy and on the PATCH each refreshing the token once (the
# PATCH retry repeating only the PATCH, not the backup), and
# destructive sync, a new file and the manifest skipping it.
make clean all SANITIZER=address,undefined
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x gdrive_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./gdrive_backup_test
echo "[pass] gdrive_backup_test (ASan)"
make clean all SANITIZER=thread
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./gdrive_backup_test
echo "[pass] gdrive_backup_test (TSan)"
@@ -259,22 +311,22 @@ jobs:
# net_http must send the same upload a slice per update, plain
# and over TLS - it used to go out in one blocking send, a call
# of a second or more (spinning at 100% CPU on a plain socket).
make clean all SANITIZER=address,undefined
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x cheevos_login_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
make check
echo "[pass] cheevos_login_test (BearSSL, ASan)"
make clean all SANITIZER=thread
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 180 make check
echo "[pass] cheevos_login_test (BearSSL, TSan)"
make clean all SSL_BACKEND=mbed SANITIZER=address,undefined
make clean SSL_BACKEND=mbed SANITIZER=address,undefined && make -j$(nproc) all SSL_BACKEND=mbed SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
make check SSL_BACKEND=mbed
echo "[pass] cheevos_login_test (mbedTLS, ASan)"
# The built-in client, RetroArch's default backend.
make clean all SSL_BACKEND=retro SANITIZER=address,undefined
make clean SSL_BACKEND=retro SANITIZER=address,undefined && make -j$(nproc) all SSL_BACKEND=retro SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
make check SSL_BACKEND=retro
@@ -294,7 +346,7 @@ jobs:
echo "$mbed_sha $mbed_tar" | sha256sum -c -
tar -xjf "$mbed_tar" -C "$RUNNER_TEMP"
make -C "$RUNNER_TEMP/mbedtls-$mbed_ver" -j"$(nproc)" lib >/dev/null
make clean all SSL_BACKEND=mbed SANITIZER=address,undefined \
make clean SSL_BACKEND=mbed SANITIZER=address,undefined && make -j$(nproc) all SSL_BACKEND=mbed SANITIZER=address,undefined \
MBEDTLS_DIR="$RUNNER_TEMP/mbedtls-$mbed_ver"
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
@@ -349,11 +401,25 @@ jobs:
echo "::error title=net_http lock::net_http.c must not take locks"
exit 1
fi
make clean all SANITIZER=address,undefined
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
./net_http_semantics_test
echo "[pass] net_http_semantics_test (ASan)"
make clean all SANITIZER=thread
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./net_http_semantics_test
echo "[pass] net_http_semantics_test (TSan)"
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+162 -40
View File
@@ -6,6 +6,13 @@ on:
- master
paths:
- 'record/**'
- 'libretro-common/net/net_rtmp*'
- 'libretro-common/include/net/net_rtmp*'
- 'libretro-common/formats/flv/**'
- 'libretro-common/include/formats/rflv_mux.h'
- 'libretro-common/samples/net/rtmp/**'
- 'libretro-common/samples/formats/flv/**'
- 'gfx/common/videotoolbox_encode*.h'
- 'libretro-common/queues/**'
- 'libretro-common/include/retro_spsc.h'
- 'libretro-common/include/retro_atomic.h'
@@ -16,6 +23,13 @@ on:
- master
paths:
- 'record/**'
- 'libretro-common/net/net_rtmp*'
- 'libretro-common/include/net/net_rtmp*'
- 'libretro-common/formats/flv/**'
- 'libretro-common/include/formats/rflv_mux.h'
- 'libretro-common/samples/net/rtmp/**'
- 'libretro-common/samples/formats/flv/**'
- 'gfx/common/videotoolbox_encode*.h'
- 'libretro-common/queues/**'
- 'libretro-common/include/retro_spsc.h'
- 'libretro-common/include/retro_atomic.h'
@@ -37,49 +51,41 @@ jobs:
samples-record:
name: Build and run samples/record
runs-on: ubuntu-latest
env:
APT_PKGS: mingw-w64 wine build-essential pkg-config ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev ccache
timeout-minutes: 25
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential pkg-config libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache@v4
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
@@ -89,11 +95,10 @@ jobs:
- name: Enable ccache
run: |
set -eu
command -v ccache >/dev/null 2>&1 || sudo apt-get install -y -qq ccache
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 500M
ccache -M 250M
ccache -z
- name: Build and run ffmpeg_pipeline_test (ASan + UBSan, TSan)
@@ -109,12 +114,129 @@ jobs:
# under contention, the full-queue wait, and the drain after
# the encoder thread is joined. Needs libavcodec with
# libx264rgb (Ubuntu's has it).
make clean all SANITIZER=address,undefined
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x ffmpeg_pipeline_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 300 \
./ffmpeg_pipeline_test
echo "[pass] ffmpeg_pipeline_test (ASan)"
make clean all SANITIZER=thread
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./ffmpeg_pipeline_test
echo "[pass] ffmpeg_pipeline_test (TSan)"
# The same run on a video ring that cannot be mirrored, as on
# a platform without a double mapping: frames that straddle
# the wrap go through the encoder side's scratch copy.
make clean SANITIZER=address,undefined PLAIN_RING=1 && make -j$(nproc) all SANITIZER=address,undefined PLAIN_RING=1
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 300 \
./ffmpeg_pipeline_test
echo "[pass] ffmpeg_pipeline_test (plain ring, ASan)"
- name: Native overlapped file output
working-directory: samples/record/file_win32
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
make clean && make check SANITIZER=address,undefined
make clean && make check CC=x86_64-w64-mingw32-gcc EXE=.exe RUNNER=wine
make clean
- name: Native MP4 mux (ASan + UBSan, mingw-w64)
shell: bash
working-directory: samples/record/mp4_mux
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
make clean && make -j$(nproc) check SANITIZER=address,undefined
make clean && make -j$(nproc) check CC=x86_64-w64-mingw32-gcc EXE=.exe RUNNER=wine
make clean
- name: Native FLV mux (ASan + UBSan, mingw-w64)
shell: bash
working-directory: libretro-common/samples/formats/flv
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
make clean && make check stream-check SANITIZER=address,undefined
make clean && make check stream-check CC=x86_64-w64-mingw32-gcc EXE=.exe RUNNER=wine
make clean
- name: RTMP protocol and sockets (ASan + UBSan, mingw-w64)
shell: bash
working-directory: libretro-common/samples/net/rtmp
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
make clean && make check peer-check SANITIZER=address,undefined
make clean && make check peer-check CC=x86_64-w64-mingw32-gcc EXE=.exe RUNNER=wine
make clean
- name: GPU PCM output pipeline (ASan + UBSan, TSan, mingw-w64)
working-directory: samples/record/gpu_av_output
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
make clean && make -j$(nproc) check SANITIZER=address,undefined
make clean && make thread_test session_test SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./thread_test
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./session_test
make clean && make -j$(nproc) check CC=x86_64-w64-mingw32-gcc EXE=.exe RUNNER=wine
make clean
- name: GPU recording dispatcher (ASan + UBSan, TSan, mingw-w64)
working-directory: samples/record/gpu_dispatch
run: |
set -eu
make clean && make check SANITIZER=address,undefined
make clean && make dispatch_test session_integration SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./dispatch_test
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./session_integration
# one Wine server for the three tests: one each, a test that
# starts as the last server leaves is cut off before main()
wineserver -p
trap 'wineserver -k || true' EXIT
make clean && make check CC=x86_64-w64-mingw32-gcc EXE=.exe RUNNER=wine
make clean
- name: Native recording frontend clock and PCM (ASan + UBSan, mingw-w64)
working-directory: samples/record/nvenc_frontend
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
make clean && make check SANITIZER=address,undefined
make clean && make check CC=x86_64-w64-mingw32-gcc EXE=.exe RUNNER=wine
make clean
# The Apple API stand-in checks ownership/configuration only. Native
# compilation and hardware encode run in MacOS-videotoolbox-encode.yml.
- name: VideoToolbox encode callback and session contracts
working-directory: samples/record/videotoolbox_encode
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
make clean && make check thread_test worker_thread_test audio_thread_test av_session_thread_test SANITIZER=address,undefined
./thread_test
./worker_thread_test
./audio_thread_test
./av_session_thread_test
make clean && make thread_test worker_thread_test audio_thread_test av_session_thread_test SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./thread_test
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./worker_thread_test
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./audio_thread_test
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./av_session_thread_test
make clean && make check CC=x86_64-w64-mingw32-gcc EXE=.exe RUNNER=wine
make clean
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+151 -41
View File
@@ -4,9 +4,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-samples-settings.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-samples-settings.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -20,52 +32,92 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
samples-settings:
name: Build and run samples/settings
ps3-user-paths:
name: PS3 per-user directories
runs-on: ubuntu-latest
timeout-minutes: 15
env:
APT_PKGS: gcc-mingw-w64-x86-64 wine64
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 15
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Regression test
working-directory: samples/frontend/ps3_user_paths
run: |
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
# Each console user's own directory, the shared USRDIR layout
# when it cannot be used, a user's first boot copying the
# shared data, and one copy per imported configuration.
make clean
make -j$(nproc) SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1 ./ps3_user_paths_test
make clean
make -j$(nproc) CC=x86_64-w64-mingw32-gcc TARGET=ps3_user_paths_test.exe
WINEDEBUG=-all timeout 300 wine ./ps3_user_paths_test.exe
make clean TARGET=ps3_user_paths_test.exe
samples-settings:
name: Build and run samples/settings
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential ccache
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache@v4
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
@@ -75,13 +127,35 @@ jobs:
- name: Enable ccache
run: |
set -eu
command -v ccache >/dev/null 2>&1 || sudo apt-get install -y -qq ccache
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 500M
ccache -M 250M
ccache -z
- name: Settings definitions keep their keys and defaults
run: |
set -eu
python3 tools/settings_check_test.py
python3 tools/settings_check.py table --check tools/settings_table.reference
python3 tools/settings_check.py unity
- name: Settings lists have room for what they are given
shell: bash
run: |
set -eu
# configuration.c builds each list of settings into an array
# of a fixed size. One setting too many for its list wrote
# past the end of the array, and RetroArch died of heap
# corruption at startup: two per-port string settings did it
# to the string list. The lists now refuse what they have no
# room for; this counts, in the preprocessed source and for
# each build profile, what every list is given against the
# size it is allocated with, so that the next one is found
# here.
timeout 300 tools/check.sh tools/settings_capacity_check.c
echo "[pass] settings_capacity_check"
- name: Build and run pipeline_migration_test (ASan + UBSan)
shell: bash
working-directory: samples/settings/pipeline_migration
@@ -98,9 +172,45 @@ jobs:
# multi-character values are untouched. The rule under test
# is restated from config_load_file(), which needs most of the
# frontend to run; a change to one must be made in the other.
make clean all SANITIZER=address,undefined
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x pipeline_migration_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./pipeline_migration_test
echo "[pass] pipeline_migration_test (ASan)"
- name: Build and run cli_override_rows_test (ASan + UBSan)
shell: bash
working-directory: samples/settings/cli_override_rows
run: |
set -eu
# Each command-line override (--log-file, --check-frames,
# --port) must pin its own setting's row: config_save_file()
# skips the row carrying a set override, so on another row it
# dropped that setting and saved the command-line value.
make clean && make check SANITIZER=address,undefined
- name: Build and run float_rows_test (ASan + UBSan)
shell: bash
working-directory: samples/settings/float_rows
run: |
set -eu
# Built without the menu: the float settings that are not the
# menu's (aspect ratio, refresh rate, fast-forward ratio, ...)
# must still be loaded and saved, and no setting of any kind
# may be listed twice.
make clean && make check SANITIZER=address,undefined
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
File diff suppressed because it is too large. Load diff
+32 -45
View File
@@ -24,44 +24,41 @@ jobs:
name: Samples no other job runs
runs-on: ubuntu-latest
timeout-minutes: 30
env:
APT_PKGS: build-essential gcc-mingw-w64-x86-64 wine64
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only -o Acquire::Retries=3 \
-o Acquire::http::Timeout=30 $pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y -o Acquire::Retries=3 \
-o Acquire::http::Timeout=30 $pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
# Up front, bounded, for the samples cross-built with mingw-w64 and
# run under Wine: installed inside the loop, a mirror that stalled
# held the job, silent, until its 30 minutes ran out
- name: Install dependencies
timeout-minutes: 15
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Run them
shell: bash
run: |
@@ -75,12 +72,9 @@ jobs:
#
# Samples that need a library the runner does not carry
# (SDL, FFmpeg, libusb) are skipped: their own workflows
# install what they need. A sample cross-built with mingw-w64
# and run under Wine gets both installed here, the first time
# one turns up, so it runs rather than fails for want of a
# compiler.
# install what they need. mingw-w64 and Wine come from the
# install step, for the samples cross-built and run under Wine.
fails=0
wine_ready=0
for mk in $(find samples libretro-common/samples -name Makefile | sort); do
dir=$(dirname "$mk")
name=$(basename "$dir")
@@ -92,13 +86,6 @@ jobs:
echo "-- skip $dir (needs a library this job does not install)"
continue
fi
if [ $wine_ready -eq 0 ] && grep -qE 'mingw32-|wine' "$mk"; then
sudo apt-get update -y -qq -o Acquire::Retries=3 \
-o Acquire::http::Timeout=30 >/dev/null || true
sudo apt-get install -y -qq gcc-mingw-w64-x86-64 wine64 \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30
wine_ready=1
fi
echo "== $dir"
if (cd "$dir" && make clean >/dev/null 2>&1 \
&& ASAN_OPTIONS=detect_leaks=1:allocator_may_return_null=1 \
@@ -49,6 +49,9 @@ jobs:
timeout-minutes: 15
steps:
- uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: state_rewind
run: make -C samples/state_rewind clean check
- name: runahead
@@ -62,6 +65,9 @@ jobs:
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
steps:
- uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: state_rewind (ASan + UBSan)
run: make -C samples/state_rewind clean check SANITIZER=address,undefined
- name: runahead (ASan + UBSan)
+44 -35
View File
@@ -18,9 +18,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-tsan-harness.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-tsan-harness.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -36,45 +48,42 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
env:
APT_PKGS: build-essential libxkbcommon-dev libx11-xcb-dev xvfb x11-utils zlib1g-dev libfreetype-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm
# Line tables are all a sanitizer report needs. configure and
# make take these in place of DEBUG=1's -O0 -g, adding their own.
CFLAGS: -O0 -g1
CXXFLAGS: -O0 -g1
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Configure
# Non-Qt, as the harness requires (with Qt, main() lives in
# ui_qt.o). Menu enabled: the menu lanes are half the point.
+77
View File
@@ -0,0 +1,77 @@
name: CI Linux TSan [sample suites]
# The threaded sample suites no other lane runs under ThreadSanitizer.
# Their own steps build them under ASan, which cannot see a race: an
# unordered access between two threads passes there and is only found
# by a developer running TSan by hand. tools/tsan-samples.sh builds each
# suite in tools/tsan-samples.list with SANITIZER=thread and runs it with
# halt_on_error, so any report fails the job; the list says what is left
# out and why.
on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-tsan-samples.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-tsan-samples.yml'
# ci_paths: end
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
linux-tsan-samples:
name: TSan, the threaded sample suites
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential zlib1g-dev libasound2-dev libsdl1.2-dev libsdl2-dev libwayland-dev ffmpeg
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Build and run each suite under TSan
run: tools/tsan-samples.sh
+43 -37
View File
@@ -27,9 +27,25 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-tsan-threaded-video.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux-tsan-threaded-video.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -43,49 +59,39 @@ jobs:
linux-tsan-threaded-video:
name: Build with TSan and run with threaded video
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential libxkbcommon-dev libx11-xcb-dev xvfb x11-utils zlib1g-dev libfreetype-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm
timeout-minutes: 45
steps:
- name: Install dependencies
# Same set as Linux-asan-ubsan.yml. libtsan ships with the gcc
# on the runner image, so nothing sanitizer-specific is needed.
timeout-minutes: 20
run: |
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Configure
# Same trimmed surface as the ASan job, for the same reason:
# a finding should be a RetroArch bug rather than noise from a
+131
View File
@@ -0,0 +1,131 @@
name: CI Linux Vulkan resize
# The Vulkan driver through changes of the swapchain's size, run for
# real: a retroarch built with Vulkan and X11, on a virtual display
# with Mesa's software Vulkan driver, has its window resized four
# times, with the menu closed and open.
#
# When a swapchain is replaced for nothing but its size, the driver
# rebuilds the framebuffers on its images and keeps the rest - the
# pipelines, render passes, samplers, pools, buffers and frame
# textures it used to destroy and build again each time. This holds
# that every size change takes that path, that the picture afterwards
# is pixel for pixel the one a full rebuild draws, and that the
# Vulkan validation layers have nothing to say about either. Then
# the same with a two-pass shader preset: the shader chain, which used
# to reflect and compile every pass again on each swapchain change, is
# kept when only the size changed, and its picture is the one a full
# rebuild draws. See samples/gfx/vulkan_resize/run.sh.
on:
push:
branches:
- master
paths:
- 'gfx/drivers/vulkan.c'
- 'gfx/common/vulkan_common.*'
- 'gfx/drivers_context/x_vk_ctx.c'
- 'gfx/drivers_shader/shader_vulkan.*'
- 'gfx/video_driver.c'
- 'gfx/video_thread_wrapper.*'
- 'samples/gfx/vulkan_resize/**'
- 'samples/input/windows_wine_smoke/smoke_core.c'
- '.github/workflows/Linux-vulkan-resize.yml'
pull_request:
paths:
- 'gfx/drivers/vulkan.c'
- 'gfx/common/vulkan_common.*'
- 'gfx/drivers_context/x_vk_ctx.c'
- 'gfx/drivers_shader/shader_vulkan.*'
- 'gfx/video_driver.c'
- 'gfx/video_thread_wrapper.*'
- 'samples/gfx/vulkan_resize/**'
- 'samples/input/windows_wine_smoke/smoke_core.c'
- '.github/workflows/Linux-vulkan-resize.yml'
workflow_dispatch:
permissions:
contents: read
# Only pull requests cancel a superseded run. On master every push
# gets its own group (keyed by the commit), so a burst of pushes queues
# instead of cancelling the run for the commit before it.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
linux-vulkan-resize:
name: Run retroarch on software Vulkan and resize its window
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential ccache xvfb xdotool x11-apps python3 libx11-dev libx11-xcb-dev libxcb1-dev libxext-dev libxxf86vm-dev libxinerama-dev libxrandr-dev libxv-dev libudev-dev zlib1g-dev libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers
timeout-minutes: 40
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Build retroarch with Vulkan and X11
shell: bash
run: |
set -eu
export CCACHE_DIR="$HOME/.ccache"
ccache -M 500M
ccache -z
# (--enable-vulkan makes configure look for the Vulkan
# loader to link against: libvulkan-dev above)
./configure --disable-qt --enable-vulkan --enable-x11 \
--disable-wayland --disable-sdl2 --disable-pulse \
--disable-alsa --disable-ffmpeg --disable-discord
make -j$(nproc) CC="ccache gcc" CXX="ccache g++"
ccache -s
test -x retroarch
- name: Resize the window on both paths
shell: bash
run: |
set -eu
timeout 600 samples/gfx/vulkan_resize/run.sh
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
File diff suppressed because it is too large. Load diff
+146
View File
@@ -0,0 +1,146 @@
name: CI Linux Wayland input
# RetroArch's Wayland input path, run for real: a retroarch built with
# Wayland and OpenGL is run under Weston - itself a window on a virtual
# X display, drawing in software - keys are pressed and the pointer is
# moved in it, and its log is read. It runs the small core of
# samples/input/windows_wine_smoke, which writes to the log what
# reaches it.
#
# What it holds: that the seat's events - keyboard, pointer - are
# handled on the frontend's thread and nowhere else, with threaded
# video and without. They are on an event queue of their own that only
# the input driver's poll dispatches; on the one queue there used to
# be, the video thread dispatched them as well. And that keys, the
# pointer, a fullscreen toggle and the keyboard focus leaving and
# coming back all still work that way. See
# samples/input/wayland_smoke/run.sh.
on:
push:
branches:
- master
paths:
- 'input/input_driver.c'
- 'input/input_driver.h'
- 'input/drivers/wayland_input.c'
- 'input/common/wayland_common.*'
- 'gfx/common/wayland_common.*'
- 'gfx/drivers_context/wayland_ctx.c'
- 'gfx/drivers_context/wayland_vk_ctx.c'
- 'gfx/video_driver.c'
- 'gfx/video_thread_wrapper.*'
- 'samples/input/wayland_smoke/**'
- 'samples/input/windows_wine_smoke/smoke_core.c'
- '.github/workflows/Linux-wayland-input.yml'
pull_request:
paths:
- 'input/input_driver.c'
- 'input/input_driver.h'
- 'input/drivers/wayland_input.c'
- 'input/common/wayland_common.*'
- 'gfx/common/wayland_common.*'
- 'gfx/drivers_context/wayland_ctx.c'
- 'gfx/drivers_context/wayland_vk_ctx.c'
- 'gfx/video_driver.c'
- 'gfx/video_thread_wrapper.*'
- 'samples/input/wayland_smoke/**'
- 'samples/input/windows_wine_smoke/smoke_core.c'
- '.github/workflows/Linux-wayland-input.yml'
workflow_dispatch:
permissions:
contents: read
# Only pull requests cancel a superseded run. On master every push
# gets its own group (keyed by the commit), so a burst of pushes queues
# instead of cancelling the run for the commit before it.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
linux-wayland-input:
name: Run retroarch under Weston and press keys
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential ccache weston xvfb xdotool x11-apps libwayland-dev wayland-protocols libxkbcommon-dev libdecor-0-dev libegl1-mesa-dev libgl1-mesa-dev libgl1-mesa-dri libudev-dev
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Build retroarch with Wayland and OpenGL (the rest off)
shell: bash
run: |
set -eu
export CCACHE_DIR="$HOME/.ccache"
ccache -M 250M
ccache -z
# What the test runs has to be real: the Wayland context and
# input driver, OpenGL, the menu, threads. Everything else is
# build time.
./configure --disable-qt --disable-x11 --enable-wayland \
--disable-sdl --disable-sdl2 --disable-ffmpeg \
--disable-vulkan --disable-pulse --disable-discord \
--disable-networking --disable-cheevos \
--disable-glslang --disable-slang \
--disable-spirv_cross --disable-xmb \
--disable-materialui --disable-ozone \
--disable-7zip --disable-flac \
--disable-audiomixer --disable-translate \
--disable-accessibility --disable-crtswitchres \
--disable-langextra --disable-libretrodb \
--disable-video_filter --disable-dsp_filter
make -j$(nproc) CC="ccache gcc" CXX="ccache g++"
ccache -s
test -x retroarch
- name: Run the scenarios under Weston
shell: bash
run: |
set -eu
timeout 600 samples/input/wayland_smoke/run.sh
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+87
View File
@@ -0,0 +1,87 @@
name: CI builtin media player
on:
push:
branches: [master]
paths:
- 'cores/libretro-media/**'
- 'samples/cores/webm_player/**'
- 'samples/gfx/d3d11_video_decode/**'
- 'libretro-common/samples/formats/audio_transfer/**'
- 'libretro-common/samples/formats/audio_transfer_wma/**'
- 'libretro-common/formats/**'
- 'libretro-common/include/formats/**'
- '.github/workflows/Linux-webm-player.yml'
pull_request:
branches: [master]
paths:
- 'cores/libretro-media/**'
- 'samples/cores/webm_player/**'
- 'samples/gfx/d3d11_video_decode/**'
- 'libretro-common/samples/formats/audio_transfer/**'
- 'libretro-common/samples/formats/audio_transfer_wma/**'
- 'libretro-common/formats/**'
- 'libretro-common/include/formats/**'
- '.github/workflows/Linux-webm-player.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
player:
runs-on: ubuntu-latest
timeout-minutes: 15
env:
APT_PKGS: build-essential ffmpeg gcc-mingw-w64-x86-64 wine64
steps:
- uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
# Bounded, with the main archive tried when the mirror stalls: a
# plain apt-get install stalled on it took the job's 15 minutes
- name: Install dependencies
timeout-minutes: 15
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Generate media
working-directory: samples/cores/webm_player
run: python3 mkfixtures.py
- name: Metadata-only demuxed audio setup
run: |
make -j2 -C libretro-common/samples/formats/audio_transfer check-empty SANITIZER=address,undefined SAMPLE_OBJDIR=obj-player-meta
env:
ASAN_OPTIONS: detect_leaks=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
- name: Run the builtin core without FFmpeg
working-directory: samples/cores/webm_player
run: |
set -eu
make -j2 check SANITIZER=address,undefined SAMPLE_OBJDIR=obj-ci-asan TARGET=webm_player_ci_asan
make -j2 check AUDIO=0 SAMPLE_OBJDIR=obj-noaudio TARGET=webm_player_noaudio SANITIZER=address,undefined
make -j2 check MP4=0 SAMPLE_OBJDIR=obj-webm TARGET=webm_player_webm SANITIZER=address,undefined
make -j2 check THREADS=0 SAMPLE_OBJDIR=obj-nothreads TARGET=webm_player_nothreads SANITIZER=address,undefined
make -j2 check-wma SANITIZER=address,undefined SAMPLE_OBJDIR=obj-wma
make -j2 check-wma WMA=0 SANITIZER=address,undefined SAMPLE_OBJDIR=obj-wma-disabled
env:
ASAN_OPTIONS: detect_leaks=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
- name: Windows core host
working-directory: samples/cores/webm_player
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
make -j2 check CC=x86_64-w64-mingw32-gcc OS=Windows_NT EXE=.exe SAMPLE_OBJDIR=obj-win RUNNER=wine
make -j2 check-wma CC=x86_64-w64-mingw32-gcc OS=Windows_NT EXE=.exe SAMPLE_OBJDIR=obj-win RUNNER=wine
+23 -2
View File
@@ -3,7 +3,19 @@ name: CI Linux (i686)
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Linux.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -19,11 +31,14 @@ concurrency:
jobs:
build:
runs-on: ubuntu-latest
# The 32-bit xenial image resolves no host on an Ubuntu 26.04 runner,
# public resolvers or not: since ubuntu-latest moved, its runs stop at
# checkout. The other container jobs, all 64-bit, are unaffected.
runs-on: ubuntu-24.04
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-i386-ubuntu:xenial-gcc9
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- name: Check Out Repo
@@ -44,6 +59,12 @@ jobs:
make -j$(getconf _NPROCESSORS_ONLN) clean
make -j$(getconf _NPROCESSORS_ONLN) info all
- name: Hidden symbol check
run: |
# libretro-common's MD5 and krb5 keep their system namesakes'
# names; exported, libssl and cores bind to them.
.github/workflows/scripts/hidden-symbols-check.sh
- name: Stack frame sweep
run: |
set -eu
+63 -7
View File
@@ -3,7 +3,23 @@ name: CI Windows (MSVC)
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/MSVC.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/MSVC.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -25,14 +41,14 @@ jobs:
timeout-minutes: 90
strategy:
matrix:
version: [UWP, 2019, 2022]
version: [2022-UWP, 2019, 2022]
configuration: [Debug, Release]
platform: [x64]
exclude:
- version: UWP
- version: 2022-UWP
configuration: Debug
include:
- version: UWP
- version: 2022-UWP
configuration: ReleaseAngle
platform: x64
# Qt and Cg builds are excluded for now
@@ -43,7 +59,7 @@ jobs:
uses: microsoft/setup-msbuild@v1
- name: Compile RA
working-directory: "${{github.workspace}}/pkg/msvc${{ matrix.version == 'UWP' && '-uwp' || ''}}"
working-directory: "${{github.workspace}}/pkg/msvc"
run: |
msbuild -p:"Configuration=${{matrix.configuration}}" -p:"Platform=${{matrix.platform}}" .\RetroArch-msvc${{matrix.version}}.sln
@@ -57,8 +73,48 @@ jobs:
with:
name: retroarch-${{matrix.version}}-${{matrix.configuration}}-${{matrix.platform}}-${{ steps.slug.outputs.sha8 }}
path: |
${{ matrix.version != 'UWP' }}:
${{ !endsWith(matrix.version, 'UWP') }}:
pkg/msvc/${{matrix.platform}}/${{matrix.configuration}}/RetroArch-msvc${{matrix.version}}.exe
${{ matrix.version == 'UWP' }}:
pkg/msvc-uwp/AppPackages/RetroArch-msvcUWP
${{ endsWith(matrix.version, 'UWP') }}:
pkg/msvc/AppPackages/RetroArch-msvc${{matrix.version}}
msvc2026:
runs-on: windows-2025
timeout-minutes: 90
strategy:
matrix:
version: [2026-UWP, 2026]
configuration: [Debug, Release]
platform: [x64]
exclude:
- version: 2026-UWP
configuration: Debug
include:
- version: 2026-UWP
configuration: ReleaseAngle
platform: x64
# Qt and Cg builds are excluded for now
steps:
- uses: actions/checkout@v7
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v3
- name: Compile RA
working-directory: "${{github.workspace}}/pkg/msvc"
run: |
msbuild -p:"Configuration=${{matrix.configuration}}" -p:"Platform=${{matrix.platform}}" .\RetroArch-msvc${{matrix.version}}.sln
- name: Get short SHA
id: slug
shell: powershell
run: echo "sha8=$('${{github.sha}}'.Substring(0,8))" >> $env:GITHUB_OUTPUT
- uses: actions/upload-artifact@v4
with:
name: retroarch-${{matrix.version}}-${{matrix.configuration}}-${{matrix.platform}}-${{ steps.slug.outputs.sha8 }}
path: |
${{ !endsWith(matrix.version, 'UWP') }}:
pkg/msvc/${{matrix.platform}}/${{matrix.configuration}}/RetroArch-msvc${{matrix.version}}.exe
${{ endsWith(matrix.version, 'UWP') }}:
pkg/msvc/AppPackages/RetroArch-msvc${{matrix.version}}
+61 -31
View File
@@ -3,7 +3,19 @@ name: CI Windows (MSYS2)
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/MSYS2.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/MSYS2.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -29,8 +41,12 @@ jobs:
runner: windows-latest
# New ARM64 build (native Windows on ARM)
# MSYS2's runtime runs emulated on these runners, so configure
# runs its checks one at a time here instead of in parallel
# background workers.
- sys: CLANGARM64
runner: windows-11-arm
qb_jobs: 1
runs-on: ${{ matrix.runner }}
timeout-minutes: 90
@@ -43,44 +59,58 @@ jobs:
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.sys }}
install: >-
base-devel
git
pacboy: >-
gettext:p
gobject-introspection:p
graphite2:p
p11-kit:p
qt6:p
qt6-3d:p
qt6-charts:p
qt6-datavis3d:p
qt6-imageformats:p
qt6-location:p
qt6-lottie:p
qt6-networkauth:p
qt6-quick3dphysics:p
qt6-quicktimeline:p
qt6-remoteobjects:p
qt6-scxml:p
qt6-sensors:p
qt6-serialbus:p
qt6-speech:p
qt6-tools:p
qt6-translations:p
qt6-virtualkeyboard:p
qt6-webchannel:p
qt6-websockets:p
x264:p
cc:p
update: true
- name: Install packages
shell: msys2 {0}
run: |
# A mirror that stalls or rate-limits (HTTP 429) fails pacman's
# whole transaction; what it fetched stays in the cache, so a
# retry fetches only the rest, one file at a time after a
# growing wait so a shared runner's rate limit can clear.
# A package that fails its checksum is deleted, but pacman
# fetches it again from the same first mirror, so each retry
# also moves every mirror list's first server to the end.
# Runtime updates belong to setup-msys2 above, which can restart
# its shell. These retries install dependencies only.
p=$MINGW_PACKAGE_PREFIX
pkgs="base-devel git $p-gettext $p-gobject-introspection
$p-graphite2 $p-p11-kit $p-qt6-base $p-x264 $p-cc"
for i in 1 2 3 4 5; do
pacman -S --noconfirm --needed $pkgs && exit 0
[ $i = 5 ] && break
echo "package install failed (attempt $i); retrying in $((30 * i))s, next mirror, serial downloads"
sed -i -e '/^#*ParallelDownloads/d' -e '/^\[options\]/a ParallelDownloads = 1' /etc/pacman.conf
for m in /etc/pacman.d/mirrorlist.*; do
awk '/^Server/ && !s { s = $0; next } { print } END { if (s) print s }' "$m" > "$m.tmp" && mv "$m.tmp" "$m"
done
sleep $((30 * i))
done
exit 1
- name: Configure and build RetroArch
shell: msys2 {0}
run: |
echo "Building RetroArch in ${{ matrix.sys }} on ${{ matrix.runner }}"
./configure
QB_JOBS=${{ matrix.qb_jobs }} ./configure
make -j"$(nproc)" info all
- name: Native modal message pump regression
shell: msys2 {0}
run: |
set -eu
flags=''
if cc --version | grep -qi clang; then flags="-Wno-c99-extensions -Wno-c11-extensions"; fi
cc $flags -std=c89 -pedantic -Wall -Wextra -Werror -Wno-unused-function -Wframe-larger-than=4096 -Ilibretro-common/include samples/gfx/win32_window_proc/modal_frame_test.c -o modal_frame_test.exe -lgdi32 -luser32
rc=0
./modal_frame_test.exe || rc=$?
if [ "$rc" = 77 ] && [ "${{ matrix.sys }}" = CLANGARM64 ]; then
echo "::notice::Modal continuity unavailable; checking safe fallback"
./modal_frame_test.exe --fallback
else
test "$rc" = 0
fi
- name: Collect DLLs and binaries
shell: msys2 {0}
run: |
+60 -2
View File
@@ -18,7 +18,23 @@ name: CI macOS PowerPC (cross)
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/MacOS-PPC-Cross.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/MacOS-PPC-Cross.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -40,7 +56,7 @@ jobs:
timeout-minutes: 60
container:
image: ghcr.io/variantxyz/gcc-powerpc-apple-darwin8:build-gcc-14.2-MacOSXSDK10.5
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
strategy:
fail-fast: false
matrix:
@@ -65,12 +81,54 @@ jobs:
--disable-glslang --disable-crtswitchres --disable-al \
--disable-cdrom
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: .ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ matrix.deployment_target }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-${{ matrix.deployment_target }}-
- name: Set up ccache
run: |
# Best effort: without ccache, ccache-make.sh runs plain make.
command -v ccache > /dev/null || {
s=$(command -v sudo || true)
for i in 1 2 3; do
$s timeout 90 apt-get -yq update \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 &&
$s timeout 120 apt-get -yq install --no-install-recommends \
ccache > /dev/null && break
echo "installing ccache failed (attempt $i); retrying in 10s"
sleep 10
done
}
echo "CCACHE_DIR=$GITHUB_WORKSPACE/.ccache" >> "$GITHUB_ENV"
echo "CCACHE_MAXSIZE=250M" >> "$GITHUB_ENV"
if command -v ccache > /dev/null; then
CCACHE_DIR="$GITHUB_WORKSPACE/.ccache" ccache -z
fi
- name: Compile RA
run: |
make ARCH=ppc -j$(getconf _NPROCESSORS_ONLN)
bash .github/workflows/scripts/ccache-make.sh ARCH=ppc -j$(getconf _NPROCESSORS_ONLN)
file retroarch
$TARGET-otool -l retroarch | grep -A2 LC_VERSION_MIN_MACOSX
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv || ccache -s
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: .ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ matrix.deployment_target }}-${{ github.sha }}
- name: Audit imports against the 10.4u SDK
if: matrix.deployment_target == '10.4'
run: |
@@ -0,0 +1,99 @@
name: CI macOS task queue samples
# The task_queue samples run on Linux and under Wine in
# Linux-libretro-common-samples.yml, which never builds the GCD runner
# Apple builds use. This runs each one with Apple clang on the threaded
# runner, and those with a GCD=1 build again on GCD: plain, under
# ASan+UBSan and under TSan.
on:
push:
branches: [master]
paths:
- 'libretro-common/queues/task_queue.c'
- 'libretro-common/include/queues/**'
- 'libretro-common/rthreads/**'
- 'libretro-common/include/rthreads/**'
- 'libretro-common/include/retro_atomic.h'
- 'libretro-common/include/retro_timers.h'
- 'libretro-common/include/features/features_cpu.h'
- 'libretro-common/include/time/rtime.h'
- 'libretro-common/features/**'
- 'libretro-common/time/rtime.c'
- 'libretro-common/samples/queues/task_queue_*/**'
- 'libretro-common/samples/sample_objdir.mk'
- '.github/workflows/MacOS-task-queue-samples.yml'
pull_request:
paths:
- 'libretro-common/queues/task_queue.c'
- 'libretro-common/include/queues/**'
- 'libretro-common/rthreads/**'
- 'libretro-common/include/rthreads/**'
- 'libretro-common/include/retro_atomic.h'
- 'libretro-common/include/retro_timers.h'
- 'libretro-common/include/features/features_cpu.h'
- 'libretro-common/include/time/rtime.h'
- 'libretro-common/features/**'
- 'libretro-common/time/rtime.c'
- 'libretro-common/samples/queues/task_queue_*/**'
- 'libretro-common/samples/sample_objdir.mk'
- '.github/workflows/MacOS-task-queue-samples.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
task-queue-macos:
name: task_queue samples on macOS, threaded and GCD
runs-on: macos-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- name: Build and run the task_queue samples
shell: bash
working-directory: libretro-common/samples/queues
env:
TSAN_OPTIONS: halt_on_error=1 second_deadlock_stack=1
UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1
run: |
set -u
set -o pipefail
# macOS has no timeout(1): perl's alarm bounds each run.
fails=0
runs=0
for d in task_queue_*; do
t=$(sed -nE 's/^TARGET[[:space:]]*:?=[[:space:]]*//p' "$d/Makefile")
runners=0
if grep -qF 'ifeq ($(GCD),1)' "$d/Makefile"; then
runners="0 1"
fi
for g in $runners; do
for san in '' address,undefined thread; do
label="$t, GCD=$g, ${san:-plain}"
printf '\n[build] %s\n' "$label"
if ! ( cd "$d" && make clean >/dev/null && make all GCD=$g SANITIZER=$san ); then
printf '::error title=macOS build failed::%s does not build\n' "$label"
fails=$((fails+1))
continue
fi
printf '[run] %s\n' "$label"
if ( cd "$d" && perl -e 'alarm shift; exec @ARGV or die "exec $ARGV[0]: $!\n"' 300 "./$t" ); then
printf '[pass] %s\n' "$label"
runs=$((runs+1))
else
rc=$?
printf '::error title=Test failed on macOS::%s exited with status %d\n' "$label" "$rc"
fails=$((fails+1))
fi
done
done
( cd "$d" && make clean >/dev/null )
done
printf '\n Ran: %d\n Failed: %d\n' "$runs" "$fails"
[[ $fails -eq 0 ]]
@@ -0,0 +1,197 @@
name: CI VideoToolbox hardware encode
on:
push:
branches: [master]
paths:
- '.github/workflows/MacOS-videotoolbox-encode.yml'
- 'gfx/common/videotoolbox_encode*.h'
- 'libretro-common/formats/h265/rh265_sps_crop.c'
- 'libretro-common/include/formats/rh265_sps_crop.h'
- 'record/drivers/record_metal.*'
- 'record/drivers/record_avfoundation*'
- 'record/record_driver.*'
- 'gfx/drivers/metal.m'
- 'Makefile.common'
- 'griffin/griffin*'
- 'record/record_gpu_capture_metal.*'
- 'record/record_gpu_session_metal.*'
- 'record/record_gpu_dispatch_metal.*'
- 'record/record_gpu_worker_vt*'
- 'record/record_audio_queue.*'
- 'record/record_avfoundation_worker*'
- 'record/record_avfoundation_session*'
- 'record/record_mux_avfoundation.*'
- 'record/shaders/capture_metal.*'
- 'record/shaders/generate_metal.py'
- 'samples/record/videotoolbox_encode/**'
- 'samples/record/metal_frontend/**'
- 'libretro-common/include/retro_atomic.h'
pull_request:
paths:
- '.github/workflows/MacOS-videotoolbox-encode.yml'
- 'gfx/common/videotoolbox_encode*.h'
- 'libretro-common/formats/h265/rh265_sps_crop.c'
- 'libretro-common/include/formats/rh265_sps_crop.h'
- 'record/drivers/record_metal.*'
- 'record/drivers/record_avfoundation*'
- 'record/record_driver.*'
- 'gfx/drivers/metal.m'
- 'Makefile.common'
- 'griffin/griffin*'
- 'record/record_gpu_capture_metal.*'
- 'record/record_gpu_session_metal.*'
- 'record/record_gpu_dispatch_metal.*'
- 'record/record_gpu_worker_vt*'
- 'record/record_audio_queue.*'
- 'record/record_avfoundation_worker*'
- 'record/record_avfoundation_session*'
- 'record/record_mux_avfoundation.*'
- 'record/shaders/capture_metal.*'
- 'record/shaders/generate_metal.py'
- 'samples/record/videotoolbox_encode/**'
- 'samples/record/metal_frontend/**'
- 'libretro-common/include/retro_atomic.h'
workflow_dispatch:
permissions:
contents: read
jobs:
encode:
runs-on: macos-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- name: Install independent test decoder
run: command -v ffmpeg >/dev/null && command -v ffprobe >/dev/null || brew install ffmpeg
- name: Native encoder and Metal device inventory
if: ${{ !cancelled() }}
run: ENCODER_DEVICE_ARTIFACT="$RUNNER_TEMP/metal-vt-devices.json" python3 samples/record/videotoolbox_encode/device_run.py
- name: Portable ownership and failure contracts
working-directory: samples/record/videotoolbox_encode
run: make check thread_test worker_thread_test audio_thread_test av_session_thread_test && ./thread_test && ./worker_thread_test && ./audio_thread_test && ./av_session_thread_test
- name: Native AAC property ABI and advertised rates
run: make -C samples/record/videotoolbox_encode native-audio-check
- name: Native hardware encoder and independent pixels
run: |
rc=0
python3 samples/record/videotoolbox_encode/run.py --native || rc=$?
if [ "$rc" = 77 ]; then
echo "::warning::Some required hardware encode cases unavailable; native encode acceptance is incomplete"
exit 0
fi
exit "$rc"
- name: Native Metal to VideoToolbox queue-worker pipeline
if: ${{ !cancelled() }}
run: |
rc=0
PIPELINE_ARTIFACT_DIR="$RUNNER_TEMP/metal-vt-evidence" python3 samples/record/videotoolbox_encode/pipeline_run.py || rc=$?
if [ "$rc" = 77 ]; then
echo "::warning::Native pipeline hardware unavailable; integrated recording acceptance is incomplete"
exit 0
fi
exit "$rc"
- name: Independent native Metal capture pixels and ownership
if: ${{ !cancelled() }}
run: |
rc=0
python3 samples/record/videotoolbox_encode/capture_run.py || rc=$?
if [ "$rc" = 77 ]; then
echo "::warning::Metal capture device unavailable; native capture acceptance is incomplete"
exit 0
fi
exit "$rc"
- name: Native compressed MP4 and AAC worker mux
if: ${{ !cancelled() }}
run: |
rc=0
PIPELINE_ARTIFACT_DIR="$RUNNER_TEMP/metal-vt-mux-evidence" python3 samples/record/videotoolbox_encode/pipeline_run.py --mux || rc=$?
if [ "$rc" = 77 ]; then
echo "::warning::Native mux hardware unavailable; A/V acceptance is incomplete"
exit 0
fi
exit "$rc"
- name: Native owned recording session
if: ${{ !cancelled() }}
run: |
rc=0
PIPELINE_ARTIFACT_DIR="$RUNNER_TEMP/metal-vt-session-evidence" python3 samples/record/videotoolbox_encode/pipeline_run.py --session || rc=$?
if [ "$rc" = 77 ]; then
echo "::warning::Native owned session hardware unavailable; controller acceptance is incomplete"
exit 0
fi
exit "$rc"
- name: Native renderer-device cold recording startup
if: ${{ !cancelled() }}
run: |
rc=0
PIPELINE_ARTIFACT_DIR="$RUNNER_TEMP/metal-vt-cold-evidence" python3 samples/record/videotoolbox_encode/pipeline_run.py --cold || rc=$?
if [ "$rc" = 77 ]; then
echo "::warning::Renderer-device encoder unavailable; cold startup acceptance is incomplete"
exit 0
fi
exit "$rc"
- name: Native dedicated recording dispatcher
if: ${{ !cancelled() }}
run: |
rc=0
PIPELINE_ARTIFACT_DIR="$RUNNER_TEMP/metal-vt-dispatch-evidence" python3 samples/record/videotoolbox_encode/pipeline_run.py --dispatch || rc=$?
if [ "$rc" = 77 ]; then
echo "::warning::Dispatcher hardware unavailable; recording dispatch acceptance is incomplete"
exit 0
fi
exit "$rc"
- name: Preserve native pipeline evidence
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: metal-vt-pipeline-evidence
path: |
${{ runner.temp }}/metal-vt-evidence
${{ runner.temp }}/metal-vt-mux-evidence
${{ runner.temp }}/metal-vt-session-evidence
${{ runner.temp }}/metal-vt-cold-evidence
${{ runner.temp }}/metal-vt-dispatch-evidence
${{ runner.temp }}/metal-vt-devices.json
if-no-files-found: warn
frontend-build:
runs-on: macos-latest
timeout-minutes: 40
steps:
- uses: actions/checkout@v6
- name: Configure native Metal recording frontend
run: |
set -eu
./configure --enable-metal --enable-avf --enable-threads --enable-configfile --enable-webmplayer \
--disable-qt --disable-ffmpeg --disable-discord --disable-cheevos \
--disable-networking --disable-sdl2 --disable-sdl --disable-translate \
--disable-update_cores --disable-update_assets --disable-online_updater \
--disable-cdrom --disable-7zip --disable-ssl
for feature in HAVE_METAL HAVE_AVF HAVE_THREADS HAVE_CONFIGFILE HAVE_WEBMPLAYER; do
grep -Eq "^#define $feature 1" config.h
done
- name: Build and link real frontend with native recording enabled
run: |
set -eu
make -j"$(sysctl -n hw.ncpu)" MINVERFLAGS="-mmacosx-version-min=$(sw_vers -productVersion)"
test -x retroarch
nm retroarch | grep '_record_metal_gpu_capture'
- name: Independent real frontend GPU recording
run: |
set -eu
command -v ffmpeg >/dev/null && command -v ffprobe >/dev/null || brew install ffmpeg
cc -dynamiclib -std=gnu89 -Ilibretro-common/include samples/record/metal_frontend/core.c -o "$RUNNER_TEMP/metal-record-core.dylib" -lm
python3 samples/record/metal_frontend/run.py --retroarch ./retroarch --core "$RUNNER_TEMP/metal-record-core.dylib" --output-dir "$RUNNER_TEMP/metal-frontend-recordings"
- uses: actions/upload-artifact@v6
if: always()
with:
name: metal-frontend-recordings
path: ${{ runner.temp }}/metal-frontend-recordings
if-no-files-found: warn
frontend-contracts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Source-clock capture and backpressure under sanitizers
run: make -C samples/record/metal_frontend check SANITIZER=address,undefined
@@ -0,0 +1,83 @@
name: CI VideoToolbox HEVC and Metal
on:
push:
branches: [master]
paths:
- '.github/workflows/MacOS-videotoolbox-hevc.yml'
- 'samples/gfx/videotoolbox_hevc/**'
- 'samples/gfx/metal_frame/**'
- 'gfx/common/videotoolbox_hevc*.h'
- 'gfx/common/metal_frame*.h'
- 'gfx/common/metal_hevc_driver.h'
- 'gfx/common/metal_vp9_driver.h'
- 'gfx/common/metal_decoder_snapshot.h'
- 'gfx/common/videotoolbox_vp9*.h'
- 'samples/gfx/videotoolbox_vp9/**'
- 'libretro-common/formats/vp9/**'
- 'libretro-common/formats/mp4/rmp4_video.c'
- 'libretro-common/formats/webm/rwebm_video.c'
- 'gfx/drivers/metal.m'
- 'gfx/drivers/metal.h'
- 'gfx/drivers/metal_shaders/Shaders.metal'
- 'gfx/video_driver.h'
- 'gfx/video_defines.h'
- 'libretro-common/formats/h265/**'
- 'libretro-common/rthreads/**'
- 'libretro-common/include/**'
pull_request:
paths:
- '.github/workflows/MacOS-videotoolbox-hevc.yml'
- 'samples/gfx/videotoolbox_hevc/**'
- 'samples/gfx/metal_frame/**'
- 'gfx/common/videotoolbox_hevc*.h'
- 'gfx/common/metal_frame*.h'
- 'gfx/common/metal_hevc_driver.h'
- 'gfx/common/metal_vp9_driver.h'
- 'gfx/common/metal_decoder_snapshot.h'
- 'gfx/common/videotoolbox_vp9*.h'
- 'samples/gfx/videotoolbox_vp9/**'
- 'libretro-common/formats/vp9/**'
- 'libretro-common/formats/mp4/rmp4_video.c'
- 'libretro-common/formats/webm/rwebm_video.c'
- 'gfx/drivers/metal.m'
- 'gfx/drivers/metal.h'
- 'gfx/drivers/metal_shaders/Shaders.metal'
- 'gfx/video_driver.h'
- 'gfx/video_defines.h'
- 'libretro-common/formats/h265/**'
- 'libretro-common/rthreads/**'
- 'libretro-common/include/**'
workflow_dispatch:
permissions:
contents: read
jobs:
videotoolbox-hevc:
runs-on: macos-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: VideoToolbox HEVC hardware decode and pixel ownership
run: |
rc=0
python3 samples/gfx/videotoolbox_hevc/run.py --native --metal || rc=$?
if [ "$rc" = 77 ]; then
echo "::warning::HEVC hardware decode or Metal shared events unavailable; native bridge check did not complete"
exit 0
fi
exit "$rc"
- name: VideoToolbox VP9 hardware decode, MP4/WebM and Metal pixels
if: ${{ !cancelled() }}
run: |
rc=0
python3 samples/gfx/videotoolbox_vp9/run.py --native --pipeline || rc=$?
if [ "$rc" = 77 ]; then
echo "::warning::VP9 hardware decode or Metal unavailable; native acceptance did not complete"
exit 0
fi
exit "$rc"
- name: Metal movie import, history and GPU pixel acceptance
if: ${{ !cancelled() }}
run: python3 samples/gfx/metal_frame/run.py --native
+31 -3
View File
@@ -3,7 +3,19 @@ name: CI macOS
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/MacOS.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/MacOS.yml'
# ci_paths: end
permissions:
contents: read
@@ -53,6 +65,16 @@ jobs:
fi
exit "$rc"
- name: Retained Metal frame lease on the native GPU
run: |
rc=0
python3 samples/gfx/metal_frame/run.py --native || rc=$?
if [ "$rc" = 77 ]; then
echo "::warning::Metal shared events unavailable; lease GPU check did not run"
exit 0
fi
exit "$rc"
- name: iCloud Drive cloud sync driver
run: |
set -o pipefail
@@ -110,6 +132,9 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Match the runner's library deployment target
run: echo "MACOSX_DEPLOYMENT_TARGET=$(sw_vers -productVersion)" >> "$GITHUB_ENV"
- name: Configure and build (Makefile, no Qt)
run: |
set -o pipefail
@@ -117,11 +142,14 @@ jobs:
--disable-networking --disable-sdl2 --disable-sdl --disable-translate \
--disable-update_cores --disable-update_assets --disable-online_updater \
--disable-cdrom --disable-7zip --disable-ssl 2>&1 | tail -5
make -j$(sysctl -n hw.ncpu) GFX_INSTRUMENT=1 2>&1 | grep -E "error|warning: unused" || true
make -j$(sysctl -n hw.ncpu) MINVERFLAGS="-mmacosx-version-min=$MACOSX_DEPLOYMENT_TARGET" GFX_INSTRUMENT=1 2>&1 | grep -E "error|warning: unused" || true
test -x retroarch
- name: Harness on Metal (RGB565 core, then XRGB8888)
env:
# Hosted macOS timers cannot reliably meet the synthetic 120 Hz grid.
HARNESS_SKIP_PHASE_GRID: '1'
run: |
set -o pipefail
SAN=none GFX_INSTRUMENT=1 DRIVER=metal CYCLES=1 make -C samples/gfx/threaded_video check-driver
HARNESS_CORE_XRGB8888=1 SAN=none GFX_INSTRUMENT=1 DRIVER=metal CYCLES=1 make -C samples/gfx/threaded_video check-driver
SAN=none GFX_INSTRUMENT=1 DRIVER=metal CYCLES=1 make -C samples/gfx/threaded_video MINVERFLAGS="-mmacosx-version-min=$MACOSX_DEPLOYMENT_TARGET" check-driver
HARNESS_CORE_XRGB8888=1 SAN=none GFX_INSTRUMENT=1 DRIVER=metal CYCLES=1 make -C samples/gfx/threaded_video MINVERFLAGS="-mmacosx-version-min=$MACOSX_DEPLOYMENT_TARGET" check-driver
+17 -1
View File
@@ -3,7 +3,23 @@ name: CI Miyoo ARM32
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Miyoo.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Miyoo.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -23,7 +39,7 @@ jobs:
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-dingux:latest
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: actions/checkout@v4
+45 -4
View File
@@ -33,9 +33,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Msg-Hash-Packed-Check.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Msg-Hash-Packed-Check.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -55,6 +67,9 @@ jobs:
- name: Checkout
uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Checker self-test
shell: bash
run: |
@@ -68,7 +83,7 @@ jobs:
# from intl/, so this reports the same thing whether or not
# the shipped headers currently pass - it is measuring the
# checker, not the tree.
python3 tools/msg_hash_packed_check.py --selftest
tools/check.sh tools/msg_hash_packed_check.c --selftest
- name: Tree check
shell: bash
@@ -80,7 +95,7 @@ jobs:
# the chunk, for a non-final chunk of a split row), no row may
# hold an embedded NUL, and the sizeof() check must still add
# up to the members it guards.
python3 tools/msg_hash_packed_check.py
tools/check.sh tools/msg_hash_packed_check.c
- name: Format parity self-test
shell: bash
@@ -94,7 +109,7 @@ jobs:
# argument taken from the table directly and through a local,
# and must not see a string that is copied, passed as a value,
# or split on a placeholder rather than formatted.
python3 tools/msg_hash_format_check.py --selftest
tools/check.sh tools/msg_hash_format_check.c --selftest
- name: Format parity tree check
shell: bash
@@ -107,4 +122,30 @@ jobs:
# That last set is measured from the call sites rather than
# assumed from the namespace: ninety rows reach a printf-family
# format argument today and twenty-nine of them are not MSG_*.
python3 tools/msg_hash_format_check.py
tools/check.sh tools/msg_hash_format_check.c
- name: Internal headers survive translation conversion
run: python3 tools/intl_conversion_check.py
- name: Label macros current
shell: bash
run: |
set -eu
# msg_hash_lbl_str.h names each label's member of the packed
# label blob and is generated from the same rows the blob is
# built from; a row added, renamed or guarded without rerunning
# the generator would leave a macro pointing at a stale or
# missing member, or fall back to a literal that duplicates
# the blob's bytes. Regenerating must be a no-op.
python3 tools/gen_lbl_str.py
git diff --exit-code -- msg_hash_lbl_str.h
- name: Sublabel map sorted
shell: bash
run: |
set -eu
# The sublabel binder looks rows up by binary search; a row
# out of order is never found and its entry loses its
# sublabel without any build failing.
python3 tools/sublabel_map_check.py --selftest
python3 tools/sublabel_map_check.py
+17 -2
View File
@@ -4,9 +4,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Overlap-Copy-Check.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Overlap-Copy-Check.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -26,6 +38,9 @@ jobs:
- name: Checkout
uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Checker self-test
shell: bash
run: |
@@ -37,7 +52,7 @@ jobs:
# allowlist suppresses audited same-base copies but can
# never suppress an exact alias, and stale allowlist
# entries are reported.
python3 tools/overlap_copy_check.py --selftest
tools/check.sh tools/overlap_copy_check.c --selftest
- name: Tree check
shell: bash
@@ -51,4 +66,4 @@ jobs:
# on Linux/Windows, so nothing else in CI can catch this
# class before it ships to Mac users. Audited-intentional
# same-buffer copies live in tools/overlap_copy_allow.list.
python3 tools/overlap_copy_check.py
tools/check.sh tools/overlap_copy_check.c
+133
View File
@@ -0,0 +1,133 @@
name: CI PS2 runtime
# Runs PS2 builds of samples/frontend/ps2_whole_file under Play!'s
# headless AutoTest (null GS), so file I/O is exercised on the console's
# own runtime - ps2sdk's libcglue and fileXio over the host: device -
# rather than a host libc standing in for it. The ELF is built in the
# same image as the PS2 release build; AutoTest is built from a pinned
# Play! commit and cached on it.
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- 'samples/frontend/ps2_whole_file/**'
- '.github/workflows/PS2-runtime.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- 'samples/frontend/ps2_whole_file/**'
- '.github/workflows/PS2-runtime.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
permissions:
contents: read
env:
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
# Play! commit AutoTest is built from; move deliberately.
PLAY_REF: 1f27232772e484bfb79364b751b84f41f4c87481
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: reallibretroretroarch/libretro-build-ps2:latest
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: actions/checkout@v4
- name: Build ps2_whole_file
working-directory: samples/frontend/ps2_whole_file
run: make PS2=1
- uses: actions/upload-artifact@v4
with:
name: ps2-runtime-elfs
path: samples/frontend/ps2_whole_file/ps2_whole_file_test.elf
run:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 45
env:
APT_PKGS: cmake ninja-build zlib1g-dev libbz2-dev libssl-dev
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v5
with:
repository: jpd002/Play-
ref: ${{ env.PLAY_REF }}
submodules: recursive
path: Play
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install build dependencies
timeout-minutes: 10
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- uses: actions/cache@v4
with:
path: Play/build
key: play-autotest-${{ runner.os }}-${{ env.PLAY_REF }}
- name: Build AutoTest
run: |
cmake -S Play -B Play/build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DBUILD_PLAY=OFF -DBUILD_TESTS=ON
cmake --build Play/build --target autotest
- uses: actions/download-artifact@v4
with:
name: ps2-runtime-elfs
path: run
- name: Run under AutoTest
shell: bash
run: |
set -euo pipefail
# AutoTest runs every .elf in the directory and writes its EE
# stdout to <name>.result; it reports a pass only against a
# .reference file, so the verdict is the harness's own lines.
# host: maps to ~/.local/share/Play Data Files/vfs/host.
timeout 600 Play/build/tools/AutoTest/autotest run
cat run/ps2_whole_file_test.result
if grep -q '^FAIL' run/ps2_whole_file_test.result; then exit 1; fi
grep -qx 'ps2_whole_file_test: PASS' run/ps2_whole_file_test.result
+17 -1
View File
@@ -3,7 +3,23 @@ name: CI PS2
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/PS2.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/PS2.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -23,7 +39,7 @@ jobs:
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-ps2:latest
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: actions/checkout@v4
+17 -1
View File
@@ -3,7 +3,23 @@ name: CI PS3/PSL1GHT
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/PS3-PSL1GHT.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/PS3-PSL1GHT.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -23,7 +39,7 @@ jobs:
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-psl1ght:latest
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: actions/checkout@v4
+30
View File
@@ -3,7 +3,23 @@ name: CI PS4/ORBIS
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/PS4-ORBIS.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/PS4-ORBIS.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -30,6 +46,20 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
# Authenticated pulls avoid the shared per-IP anonymous rate limit;
# without the secrets (fork pull requests) the pull stays anonymous
- name: Log in to Docker Hub
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
if [ -n "$DOCKERHUB_USERNAME" ] && [ -n "$DOCKERHUB_TOKEN" ]; then
echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
fi
- name: Compile RA
run: |
docker run --rm --user root -v "$PWD":/src -w /src "$ORBIS_IMAGE" bash -c '
+18 -2
View File
@@ -3,7 +3,23 @@ name: CI PSP
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/PSP.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/PSP.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -23,14 +39,14 @@ jobs:
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-psp:latest
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: actions/checkout@v4
- name: Compile bootstrap
run: |
cd bootstrap/psp1/kernel_functions_prx
cd os/psp1/kernel_functions_prx
make -j$(getconf _NPROCESSORS_ONLN) clean
make -j$(getconf _NPROCESSORS_ONLN)
+61 -2
View File
@@ -3,7 +3,23 @@ name: CI PSVita
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/PSVita.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/PSVita.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -23,7 +39,7 @@ jobs:
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-vita:latest-20260905
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: actions/checkout@v4
@@ -33,10 +49,53 @@ jobs:
make -f Makefile.vita.salamander -j$(getconf _NPROCESSORS_ONLN) clean
make -f Makefile.vita.salamander -j$(getconf _NPROCESSORS_ONLN)
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: .ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Set up ccache
run: |
# Best effort: without ccache, ccache-make.sh runs plain make.
command -v ccache > /dev/null || {
s=$(command -v sudo || true)
for i in 1 2 3; do
$s timeout 90 apt-get -yq update \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 &&
$s timeout 120 apt-get -yq install --no-install-recommends \
ccache > /dev/null && break
echo "installing ccache failed (attempt $i); retrying in 10s"
sleep 10
done
}
echo "CCACHE_DIR=$GITHUB_WORKSPACE/.ccache" >> "$GITHUB_ENV"
echo "CCACHE_MAXSIZE=250M" >> "$GITHUB_ENV"
if command -v ccache > /dev/null; then
CCACHE_DIR="$GITHUB_WORKSPACE/.ccache" ccache -z
fi
- name: Compile RA
run: |
make -f Makefile.vita -j$(getconf _NPROCESSORS_ONLN) clean
make -f Makefile.vita -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1 HAVE_VITAGLES=1 info all
bash .github/workflows/scripts/ccache-make.sh -f Makefile.vita -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1 HAVE_VITAGLES=1 info all
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv || ccache -s
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: .ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
- name: Get short SHA
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
+65
View File
@@ -0,0 +1,65 @@
name: CI prune caches
# Every master push saves a ccache per job under a key ending in the
# commit, and only the newest of each is ever restored. The older ones
# fill the repository's cache store, which then evicts the newest cache
# of any workflow that has not run for a few hours. This keeps only the
# newest entry for each job and branch.
on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Prune-caches.yml'
# ci_paths: end
schedule:
- cron: '23 * * * *'
workflow_dispatch:
permissions:
actions: write
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
prune:
name: Delete superseded caches
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Delete all but the newest cache of each job
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
run: |
set -eu
# Only ccache keys ending in a commit, with the commit cut off;
# jq does it, as some mawk versions have no {n} in a regex.
gh api --paginate \
"repos/$REPO/actions/caches?per_page=100&sort=created_at&direction=desc" \
--jq '.actions_caches[]
| select(.key | test("^ccache-.*-[0-9a-f]{40}$"))
| [.id, .ref, (.key | sub("-[0-9a-f]{40}$"; "")), .size_in_bytes]
| @tsv' \
> caches.tsv
# Newest first: a cache is superseded once a newer one of the
# same key on the same branch has been seen. A cache saved
# while paging can list one twice, so each id counts once.
awk -F '\t' '!ids[$1]++ && seen[$2 FS $3]++ { print $1 "\t" $4 }' \
caches.tsv > prune.tsv
awk -F '\t' '{ n++; s += $2 } END {
printf "%d commit-keyed ccaches, %d superseded (%.2f GB)\n",
NR0, n, s / 1e9 }' \
NR0="$(wc -l < caches.tsv)" prune.tsv
cut -f1 prune.tsv | while read -r id; do
gh api -X DELETE "repos/$REPO/actions/caches/$id" > /dev/null || \
echo "could not delete cache $id (gone already?)"
done
+13 -4
View File
@@ -31,12 +31,18 @@ on:
push:
paths:
- 'menu/drivers/rgui.c'
- 'tools/rgui_pixfmt_check.py'
- 'tools/rgui_pixfmt_check.c'
- 'tools/rgui_pixfmt_harness.inc'
- 'tools/check_common.h'
- 'tools/check.sh'
- '.github/workflows/RGUI-Pixel-Format-Check.yml'
pull_request:
paths:
- 'menu/drivers/rgui.c'
- 'tools/rgui_pixfmt_check.py'
- 'tools/rgui_pixfmt_check.c'
- 'tools/rgui_pixfmt_harness.inc'
- 'tools/check_common.h'
- 'tools/check.sh'
- '.github/workflows/RGUI-Pixel-Format-Check.yml'
workflow_dispatch:
@@ -57,6 +63,9 @@ jobs:
- name: Checkout
uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Checker self-test
shell: bash
run: |
@@ -70,7 +79,7 @@ jobs:
# either way, so the self-test reports the same thing
# whether or not the tree is currently fixed - it is
# measuring the checker, not the tree.
python3 tools/rgui_pixfmt_check.py --selftest --cc gcc
tools/check.sh tools/rgui_pixfmt_check.c --selftest --cc gcc
- name: Tree check
shell: bash
@@ -82,4 +91,4 @@ jobs:
# an empty and a full field; the field must be monotonic in
# the input; and the dithered twin must cover the same bits
# and stay within one level of the plain converter.
python3 tools/rgui_pixfmt_check.py --cc gcc
tools/check.sh tools/rgui_pixfmt_check.c --cc gcc
+17 -1
View File
@@ -3,7 +3,23 @@ name: CI RS90 Odbeta MIPS32
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/RS90.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/RS90.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -23,7 +39,7 @@ jobs:
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-dingux:odbeta
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: actions/checkout@v4
+20 -2
View File
@@ -30,9 +30,21 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Removed-Member-Check.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Removed-Member-Check.yml'
# ci_paths: end
workflow_dispatch:
permissions:
@@ -55,6 +67,12 @@ jobs:
# Both ends of the range have to be present, and a pull
# request's base can be behind by any amount.
fetch-depth: 0
# The history without its file contents: the diff fetches
# the few it reads. A fifth of the clone.
filter: blob:none
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Checker self-test
shell: bash
@@ -71,7 +89,7 @@ jobs:
# The fixtures are handed to the checker directly rather than
# taken from the tree, so this reports the same thing whether
# or not the tree currently passes - it measures the checker.
python3 tools/removed_member_check.py --selftest
tools/check.sh tools/removed_member_check.c --selftest
- name: Range check
shell: bash
@@ -101,7 +119,7 @@ jobs:
# Every struct member the range removes, minus the ones the
# tree still declares somewhere, against every .c .h .m .mm
# .cpp .hpp and .inc reader of one.
python3 tools/removed_member_check.py --range "$base..$head"
tools/check.sh tools/removed_member_check.c --range "$base..$head"
env:
PR_BASE: ${{ github.event.pull_request.base.sha }}
PR_HEAD: ${{ github.event.pull_request.head.sha }}
+17 -1
View File
@@ -3,7 +3,23 @@ name: CI RetroFW MIPS32
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/RetroFW.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/RetroFW.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -23,7 +39,7 @@ jobs:
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-dingux:odbeta
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: actions/checkout@v4
@@ -0,0 +1,56 @@
name: CI Sample object directories
on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Samples-Objdir-Check.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/Samples-Objdir-Check.yml'
# ci_paths: end
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
sample-objdir-check:
name: Every sample builds its objects in its own directory
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Checker self-test
shell: bash
run: |
set -eu
tools/check.sh tools/sample_objdir_check.c --selftest
- name: Tree check
shell: bash
run: |
set -eu
# unity_identity once linked the ASan objects mic_handshake
# left beside the shared sources.
tools/check.sh tools/sample_objdir_check.c
+11 -3
View File
@@ -8,6 +8,14 @@ on:
push:
tags:
- "v*.*"
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/SourceRelease.yml'
# ci_paths: end
env:
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
@@ -23,6 +31,9 @@ jobs:
with:
submodules: recursive
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: archive
id: archive
run: |
@@ -34,9 +45,6 @@ jobs:
mv * /tmp/$PKGNAME
mv /tmp/$PKGNAME .
rm -rf $PKGNAME/pkg || true
rm -rf $PKGNAME/wii/libogc || true
rm -rf $PKGNAME/deps/glslang/glslang/Test || true
rm -rf $PKGNAME/deps/SPIRV-Cross/reference || true
rm -rf $PKGNAME/gfx/include/userland || true
find $PKGNAME/ -type f -name '*.a' -delete || true
find $PKGNAME/ -type f -name '*.lib' -delete || true
+61 -2
View File
@@ -3,7 +3,23 @@ name: CI Switch/libnx
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Switch-libnx.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Switch-libnx.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -23,15 +39,58 @@ jobs:
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-libnx-devkitpro:v4-10-0
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: actions/checkout@v4
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: .ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Set up ccache
run: |
# Best effort: without ccache, ccache-make.sh runs plain make.
command -v ccache > /dev/null || {
s=$(command -v sudo || true)
for i in 1 2 3; do
$s timeout 90 apt-get -yq update \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 &&
$s timeout 120 apt-get -yq install --no-install-recommends \
ccache > /dev/null && break
echo "installing ccache failed (attempt $i); retrying in 10s"
sleep 10
done
}
echo "CCACHE_DIR=$GITHUB_WORKSPACE/.ccache" >> "$GITHUB_ENV"
echo "CCACHE_MAXSIZE=250M" >> "$GITHUB_ENV"
if command -v ccache > /dev/null; then
CCACHE_DIR="$GITHUB_WORKSPACE/.ccache" ccache -z
fi
- name: Compile RA
run: |
make -f Makefile.libnx -j$(getconf _NPROCESSORS_ONLN) clean
make -f Makefile.libnx -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1
bash .github/workflows/scripts/ccache-make.sh -f Makefile.libnx -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv || ccache -s
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: .ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
- name: Get short SHA
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
+63 -4
View File
@@ -3,7 +3,23 @@ name: CI Wii
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Wii.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Wii.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -22,8 +38,8 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-devkitpro:latest
options: --user root
image: devkitpro/devkitppc:latest
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: taiki-e/checkout-action@v1
@@ -31,9 +47,52 @@ jobs:
- name: Compile Salamander
run: |
make -f Makefile.wii.salamander -j$(getconf _NPROCESSORS_ONLN) clean
make -f Makefile.wii.salamander -j$(getconf _NPROCESSORS_ONLN) EXTERNAL_LIBOGC=1 GX_PTHREAD_LEGACY=0
make -f Makefile.wii.salamander -j$(getconf _NPROCESSORS_ONLN)
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: .ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Set up ccache
run: |
# Best effort: without ccache, ccache-make.sh runs plain make.
command -v ccache > /dev/null || {
s=$(command -v sudo || true)
for i in 1 2 3; do
$s timeout 90 apt-get -yq update \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 &&
$s timeout 120 apt-get -yq install --no-install-recommends \
ccache > /dev/null && break
echo "installing ccache failed (attempt $i); retrying in 10s"
sleep 10
done
}
echo "CCACHE_DIR=$GITHUB_WORKSPACE/.ccache" >> "$GITHUB_ENV"
echo "CCACHE_MAXSIZE=250M" >> "$GITHUB_ENV"
if command -v ccache > /dev/null; then
CCACHE_DIR="$GITHUB_WORKSPACE/.ccache" ccache -z
fi
- name: Compile RA
run: |
make -f Makefile.wii -j$(getconf _NPROCESSORS_ONLN) clean
make -f Makefile.wii -j$(getconf _NPROCESSORS_ONLN) EXTERNAL_LIBOGC=1 GX_PTHREAD_LEGACY=0 HAVE_STATIC_DUMMY=1 info all
bash .github/workflows/scripts/ccache-make.sh -f Makefile.wii -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1 info all
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv || ccache -s
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: .ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+17 -1
View File
@@ -3,7 +3,23 @@ name: CI WiiU
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/WiiU.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/WiiU.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -23,7 +39,7 @@ jobs:
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-wiiu:latest
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: taiki-e/checkout-action@v1
+16
View File
@@ -3,7 +3,23 @@ name: CI Windows ARM64 (MSVC)
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Windows-ARM64.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Windows-ARM64.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
release:
+46 -2
View File
@@ -3,7 +3,23 @@ name: CI Windows i686 (MXE)
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Windows-i686-MXE.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Windows-i686-MXE.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -23,14 +39,42 @@ jobs:
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-mxe-win32-cross:gcc16
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: taiki-e/checkout-action@v1
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Compile RA
run: |
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
ccache -M 250M
ccache -z
export MOC=/usr/lib/mxe/usr/i686-w64-mingw32.shared/qt5/bin/moc
./configure --host=i686-w64-mingw32.shared
CC="ccache i686-w64-mingw32.shared-gcc" \
CXX="ccache i686-w64-mingw32.shared-g++" \
./configure --host=i686-w64-mingw32.shared
make clean
make -j$(getconf _NPROCESSORS_ONLN)
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+208
View File
@@ -0,0 +1,208 @@
name: CI Windows raw input under Wine
# The Windows build's raw input path, run for real. The other winraw
# tests take the drivers apart and stand in for the frontend around
# them; this one cross-builds retroarch.exe with mingw-w64, runs it
# under Wine on a virtual display with the GDI video driver, the raw
# input driver and the raw input joypad driver, presses keys in its
# window and reads its log.
#
# It runs a small core that writes to the log what reaches it. Four
# scenarios, each: two keys, a fullscreen toggle (which restarts the
# GDI video driver), the two keys again, quit with Escape - and in each
# the core must have seen its button and its key events before the
# restart and after it. Read by the poll and kept across the restart
# (the default); restarted with the video driver
# (RETROARCH_INPUT_KEEP=0); threaded video off; and DirectInput.
# In all of those the window is left up across both restarts and
# taken back; two more destroy it with the driver as before
# (RETROARCH_WINDOW_KEEP=0) and leave it up without taking it (=2).
# Those use the GDI video driver. Two more use OpenGL on Wine's
# software rasteriser: the fullscreen toggle restyling the window in
# place, and - with that turned off - restarting on the kept window.
# See samples/input/windows_wine_smoke/run.sh.
#
# Wine is not Windows: what this holds is that the frontend, the pump,
# the two drivers and the driver restart fit together, on every commit
# that touches them. What only Windows answers - whether its raw input
# reaches a window on a thread without the focus, what a real
# controller sends - was tried by hand when the path was made.
on:
push:
branches:
- master
paths:
- 'input/input_driver.c'
- 'input/input_driver.h'
- 'input/input_driver_choice.c'
- 'input/drivers/winraw_input.c'
- 'input/drivers/winraw_mouse_pending.h'
- 'input/drivers/winraw_mouse_buttons.h'
- 'input/drivers_joypad/winraw_joypad.c'
- 'gfx/common/win32_common.c'
- 'gfx/common/win32_msg_route.h'
- 'gfx/common/win32_ime.h'
- 'samples/input/win32_ime/**'
- 'gfx/display_servers/dispserv_win32.c'
- 'gfx/drivers/gdi_gfx.c'
- 'gfx/drivers_context/wgl_ctx.c'
- 'gfx/video_driver.c'
- 'gfx/video_thread_wrapper.*'
- 'ui/drivers/ui_win32.c'
- 'retroarch.c'
- 'runloop.c'
- 'samples/input/windows_wine_smoke/**'
- '.github/workflows/Windows-wine-input.yml'
pull_request:
paths:
- 'input/input_driver.c'
- 'input/input_driver.h'
- 'input/input_driver_choice.c'
- 'input/drivers/winraw_input.c'
- 'input/drivers/winraw_mouse_pending.h'
- 'input/drivers/winraw_mouse_buttons.h'
- 'input/drivers_joypad/winraw_joypad.c'
- 'gfx/common/win32_common.c'
- 'gfx/common/win32_msg_route.h'
- 'gfx/common/win32_ime.h'
- 'samples/input/win32_ime/**'
- 'gfx/display_servers/dispserv_win32.c'
- 'gfx/drivers/gdi_gfx.c'
- 'gfx/video_driver.c'
- 'gfx/video_thread_wrapper.*'
- 'ui/drivers/ui_win32.c'
- 'retroarch.c'
- 'runloop.c'
- 'samples/input/windows_wine_smoke/**'
- '.github/workflows/Windows-wine-input.yml'
workflow_dispatch:
permissions:
contents: read
# Only pull requests cancel a superseded run. On master every push
# gets its own group (keyed by the commit), so a burst of pushes queues
# instead of cancelling the run for the commit before it.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
windows-wine-input:
name: Run retroarch.exe under Wine and press keys
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential ccache gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 wine64 xvfb xdotool libgl1-mesa-dri
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Build and run win32_ime_test under Wine
shell: bash
run: |
set -eu
# The window has its input context (the IME) only while a line
# of text is open: gfx/common/win32_ime.h, driven from another
# thread as the frontend does, the window asked on its own.
Xvfb :97 -screen 0 640x480x24 > /dev/null 2>&1 &
trap 'kill $! 2>/dev/null; wineserver -k >/dev/null 2>&1 || true' EXIT
export DISPLAY=:97
make -C samples/input/win32_ime check
make -C samples/input/win32_ime clean
- name: Build and run winusb_hid_test under Wine
shell: bash
run: |
set -eu
# The WinUSB HID driver with the real pad handlers and a
# stand-in for winusb.dll: a DualShock 3 started by HID class
# requests on the control pipe, the newest report its reader
# has had taken at the poll, its Bluetooth address read and
# set, and a pad that goes or a driver that stops letting the
# reader go.
trap 'wineserver -k >/dev/null 2>&1 || true' EXIT
make -C samples/input/winusb_hid check
make -C samples/input/winusb_hid clean
- name: Build retroarch.exe with mingw-w64 (GDI and raw input, the rest off)
shell: bash
run: |
set -eu
export CCACHE_DIR="$HOME/.ccache"
ccache -M 250M
ccache -z
# What the test runs has to be real: the Win32 window code,
# the GDI and OpenGL drivers, the raw input drivers, the
# menu, threads. Everything else is build time.
./configure --host=x86_64-w64-mingw32 \
--disable-qt --disable-vulkan --disable-ffmpeg \
--disable-networking --disable-cheevos \
--disable-glslang --disable-slang \
--disable-spirv_cross --disable-xmb \
--disable-materialui --disable-ozone \
--disable-7zip --disable-flac \
--disable-audiomixer --disable-translate \
--disable-accessibility --disable-langextra \
--disable-libretrodb --disable-discord \
--disable-d3d9 --disable-d3d10 --disable-d3d11 \
--disable-d3d12 --disable-sdl \
--disable-sdl2 --disable-video_filter \
--disable-dsp_filter --disable-crtswitchres
make -j$(nproc) CC="ccache x86_64-w64-mingw32-gcc" \
CXX="ccache x86_64-w64-mingw32-g++"
ccache -s
test -f retroarch.exe
- name: Run the scenarios under Wine
shell: bash
run: |
set -eu
# Stop wineserver on exit so it cannot hold the step's output
# open.
trap 'wineserver -k >/dev/null 2>&1 || true' EXIT
timeout 900 samples/input/windows_wine_smoke/run.sh
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+242
View File
@@ -0,0 +1,242 @@
name: CI Windows menus under Wine
# Ozone, XMB, MaterialUI and RGUI as each Direct3D driver and both GL
# drivers draw them. Cross-builds retroarch.exe with mingw-w64, starts
# it with no content under Wine on a virtual display for each menu on
# each driver, reads the screen once it holds still and compares it
# with D3D11's block by block. A solid quad sampling the wrong texture
# (d3d9_hlsl's batched quads read the font atlas) or a gradient split
# along the other diagonal (both D3D9 drivers' single quads) moves
# whole blocks; the drivers' own text and edge differences do not.
# See samples/gfx/menu_wine/run.py. Then a core writes its frames into
# the framebuffer d3d9_hlsl lends it - still, cropped, and through the
# driver growing its textures - checked against D3D11's copy
# (samples/gfx/menu_wine/swfb.py).
on:
push:
paths:
- 'gfx/drivers/d3d*.c'
- 'gfx/drivers/gl2.c'
- 'gfx/drivers/gl3.c'
- 'gfx/drivers/d3d_shaders/**'
- 'gfx/common/d3d*'
- 'gfx/common/gl*'
- 'gfx/gfx_display.*'
- 'gfx/font_driver.*'
- 'gfx/font_layout.h'
- 'gfx/drivers_font/**'
- 'gfx/drivers_font_renderer/**'
- 'menu/drivers/**'
- 'menu/menu_driver.*'
- 'samples/gfx/menu_wine/**'
- 'samples/gfx/d3d12_menu_mapping/**'
- 'samples/gfx/threaded_video/harness_core.c'
- 'samples/gfx/d3d9_ex/**'
- '.github/workflows/Windows-wine-menu.yml'
pull_request:
paths:
- 'gfx/drivers/d3d*.c'
- 'gfx/drivers/gl2.c'
- 'gfx/drivers/gl3.c'
- 'gfx/drivers/d3d_shaders/**'
- 'gfx/common/d3d*'
- 'gfx/common/gl*'
- 'gfx/gfx_display.*'
- 'gfx/font_driver.*'
- 'gfx/font_layout.h'
- 'gfx/drivers_font/**'
- 'gfx/drivers_font_renderer/**'
- 'menu/drivers/**'
- 'menu/menu_driver.*'
- 'samples/gfx/menu_wine/**'
- 'samples/gfx/d3d12_menu_mapping/**'
- 'samples/gfx/threaded_video/harness_core.c'
- 'samples/gfx/d3d9_ex/**'
- '.github/workflows/Windows-wine-menu.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
windows-wine-menu:
name: Menus on D3D8, D3D9, D3D10, D3D11, D3D12 and GL under Wine
runs-on: ubuntu-24.04
env:
APT_PKGS: build-essential ccache gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 mingw-w64-tools wine64 xvfb xauth imagemagick libgl1-mesa-dri mesa-vulkan-drivers libvulkan1 autoconf automake libtool flex bison pkg-config libvulkan-dev spirv-headers python3
timeout-minutes: 90
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Build vkd3d's shader compiler for Windows
shell: bash
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
v="$RUNNER_TEMP/vkd3d"
git clone -q --depth 1 --branch vkd3d-2.1 \
https://gitlab.winehq.org/wine/vkd3d.git "$v"
cd "$v"
./autogen.sh > /dev/null
mkdir -p inc lib build
ln -s /usr/include/vulkan inc/vulkan
ln -s /usr/include/vk_video inc/vk_video
ln -s /usr/include/spirv inc/spirv
printf 'LIBRARY vulkan-1.dll\nEXPORTS\nvkGetInstanceProcAddr\n' \
> lib/vulkan-1.def
x86_64-w64-mingw32-dlltool -d lib/vulkan-1.def \
-l lib/libvulkan-1.a
cd build
../configure -q --host=x86_64-w64-mingw32 --disable-tests \
--disable-demos WIDL=x86_64-w64-mingw32-widl \
CPPFLAGS="-I$v/inc" LDFLAGS="-L$v/lib"
make -j$(nproc) > /dev/null
cp .libs/libvkd3d-utils-1.dll "$GITHUB_WORKSPACE/D3DCompiler_47.dll"
cp .libs/libvkd3d-shader-1.dll .libs/libvkd3d-1.dll \
"$GITHUB_WORKSPACE/"
- name: Build retroarch.exe with mingw-w64 (D3D8, D3D9, D3D10, D3D11, D3D12, GL)
shell: bash
run: |
set -eu
export CCACHE_DIR="$HOME/.ccache"
ccache -M 250M
./configure --host=x86_64-w64-mingw32 \
--disable-qt --disable-vulkan --disable-ffmpeg \
--disable-cheevos --disable-discord --enable-d3d8 \
--disable-sdl --disable-sdl2
make -j$(nproc) CC="ccache x86_64-w64-mingw32-gcc" \
CXX="ccache x86_64-w64-mingw32-g++"
ccache -s
for f in libstdc++-6.dll libgcc_s_seh-1.dll; do
cp "$(ls /usr/lib/gcc/x86_64-w64-mingw32/*-win32/$f | head -n 1)" .
done
- name: Menus on every driver, compared with D3D11 (Wine, Xvfb, llvmpipe)
shell: bash
run: |
set -eu
trap 'wineserver -k >/dev/null 2>&1 || true' EXIT
export LIBGL_ALWAYS_SOFTWARE=1
WINEDEBUG=-all timeout 120 xvfb-run -a sh -c 'wineboot -i && wineserver -w'
python3 samples/gfx/menu_wine/run.py ./retroarch.exe \
"$RUNNER_TEMP/menu-wine" d3d11 d3d10 d3d12 d3d9_hlsl d3d8 gl glcore
- name: D3D12 menu component mapping on the GPU (Wine)
shell: bash
run: |
set -eu
trap 'wineserver -k >/dev/null 2>&1 || true' EXIT
export LIBGL_ALWAYS_SOFTWARE=1
CC=x86_64-w64-mingw32-gcc RUNNER=wine \
xvfb-run -a python3 samples/gfx/d3d12_menu_mapping/run.py
CC=x86_64-w64-mingw32-gcc RUNNER=wine REQUIRE_D3D11=1 \
xvfb-run -a python3 samples/gfx/d3d_failure_paths/gpu.py
CC=x86_64-w64-mingw32-gcc RUNNER=wine REQUIRE_D3D11=1 \
xvfb-run -a python3 samples/gfx/d3d_failure_paths/lend_gpu.py
CC=x86_64-w64-mingw32-gcc RUNNER=wine REQUIRE_D3D11=1 \
xvfb-run -a python3 samples/gfx/d3d_failure_paths/sw_lend_gpu.py
- name: A core writing into the framebuffer d3d9_hlsl lends it (Wine)
shell: bash
run: |
set -eu
trap 'wineserver -k >/dev/null 2>&1 || true' EXIT
(cd samples/gfx/threaded_video && x86_64-w64-mingw32-gcc -O1 \
-shared -I../../../libretro-common/include -I../../../gfx/include \
-o ../../../harness_core.dll harness_core.c)
python3 samples/gfx/menu_wine/swfb.py ./retroarch.exe \
./harness_core.dll "$RUNNER_TEMP/menu-wine/swfb" d3d9_hlsl
- name: D3D9 textures and frame memory on a Direct3D9Ex device and a plain one (Wine)
shell: bash
run: |
set -eu
trap 'wineserver -k >/dev/null 2>&1 || true' EXIT
# What reached the GPU, read back: the managed pool before Ex,
# default-pool textures with system-memory twins on it, and the
# frames it reads where they lie; then a device in the flip
# model, its frame latency and d3d9_present's ways to wait
make -C samples/gfx/d3d9_ex check
- name: d3d9_hlsl with Direct3D 9Ex Presentation, and without Ex (Wine)
shell: bash
run: |
set -eu
trap 'wineserver -k >/dev/null 2>&1 || true' EXIT
# The window in the flip model and the frame latency taken on
# Ex, the same screens; as before where there is no Ex
export SWFB_D3D9EX_PRESENT=2
python3 samples/gfx/menu_wine/swfb.py ./retroarch.exe \
./harness_core.dll "$RUNNER_TEMP/menu-wine/swfb-present" d3d9_hlsl
RETROARCH_D3D9EX=0 python3 samples/gfx/menu_wine/swfb.py \
./retroarch.exe ./harness_core.dll \
"$RUNNER_TEMP/menu-wine/swfb-present-legacy" d3d9_hlsl
- name: d3d9_hlsl without Direct3D9Ex, as a system without it has (Wine)
shell: bash
run: |
set -eu
trap 'wineserver -k >/dev/null 2>&1 || true' EXIT
export RETROARCH_D3D9EX=0
python3 samples/gfx/menu_wine/run.py ./retroarch.exe \
"$RUNNER_TEMP/menu-wine/legacy" d3d11 d3d9_hlsl
python3 samples/gfx/menu_wine/swfb.py ./retroarch.exe \
./harness_core.dll "$RUNNER_TEMP/menu-wine/swfb-legacy" d3d9_hlsl
- name: Preserve screens on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: windows-wine-menu-screens
path: |
${{ runner.temp }}/menu-wine/**/*.png
${{ runner.temp }}/menu-wine/**/*.log
if-no-files-found: ignore
- name: Save ccache
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+194
View File
@@ -0,0 +1,194 @@
name: CI Windows views under Wine
# Views on the GDI, D3D8, D3D9 HLSL, D3D10, D3D11 and D3D12 drivers, end
# to end: retroarch.exe cross-built with mingw-w64 runs the video_views
# test core under Wine, on a virtual display through Mesa's software
# rasterisers (D3D12 on Wine's vkd3d over lavapipe), and
# samples/cores/video_views/e2e/run.py checks the screen in each stereo
# mode, from the core's software frames and its D3D11 and D3D12
# textures, with the menu laid out per eye and the pointer over it.
# Wine's own shader compiler can't take the drivers' HLSL, so vkd3d's,
# from its release, is put in as D3DCompiler_47.dll.
on:
push:
branches:
- master
paths:
- 'gfx/common/d3d9_*'
- 'gfx/drivers/gdi_gfx.c'
- 'gfx/common/gdi_defines.h'
- 'gfx/drivers/d3d8.c'
- 'gfx/drivers/d3d9hlsl.c'
- 'gfx/drivers/d3d10.c'
- 'gfx/drivers/d3d11.c'
- 'gfx/drivers/d3d12.c'
- 'gfx/drivers/d3d_shaders/views_sm*.hlsl.h'
- 'gfx/video_views.*'
- 'gfx/video_views_draw.h'
- 'gfx/video_driver.c'
- 'gfx/video_thread_wrapper.*'
- 'configuration.*'
- 'samples/settings/video_screens/**'
- 'samples/cores/video_views/**'
- '.github/workflows/Windows-wine-views.yml'
pull_request:
paths:
- 'gfx/common/d3d9_*'
- 'gfx/drivers/gdi_gfx.c'
- 'gfx/common/gdi_defines.h'
- 'gfx/drivers/d3d8.c'
- 'gfx/drivers/d3d9hlsl.c'
- 'gfx/drivers/d3d10.c'
- 'gfx/drivers/d3d11.c'
- 'gfx/drivers/d3d12.c'
- 'gfx/drivers/d3d_shaders/views_sm*.hlsl.h'
- 'gfx/video_views.*'
- 'gfx/video_views_draw.h'
- 'gfx/video_driver.c'
- 'gfx/video_thread_wrapper.*'
- 'configuration.*'
- 'samples/settings/video_screens/**'
- 'samples/cores/video_views/**'
- '.github/workflows/Windows-wine-views.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
windows-wine-views:
name: Views on GDI, D3D8, D3D9, D3D10, D3D11 and D3D12 under Wine
runs-on: ubuntu-24.04
env:
APT_PKGS: build-essential ccache gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 mingw-w64-tools wine64 xvfb xauth imagemagick libgl1-mesa-dri mesa-vulkan-drivers libvulkan1 autoconf automake libtool flex bison pkg-config libvulkan-dev spirv-headers python3
timeout-minutes: 150
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Build vkd3d's shader compiler for Windows
shell: bash
run: |
set -eu
. "$GITHUB_WORKSPACE/.github/workflows/scripts/wine-server.sh"
v="$RUNNER_TEMP/vkd3d"
git clone -q --depth 1 --branch vkd3d-2.1 \
https://gitlab.winehq.org/wine/vkd3d.git "$v"
cd "$v"
./autogen.sh > /dev/null
# Its headers from the host; vulkan-1.dll is loaded at run
# time, so an import library naming it does.
mkdir -p inc lib build
ln -s /usr/include/vulkan inc/vulkan
ln -s /usr/include/vk_video inc/vk_video
ln -s /usr/include/spirv inc/spirv
printf 'LIBRARY vulkan-1.dll\nEXPORTS\nvkGetInstanceProcAddr\n' \
> lib/vulkan-1.def
x86_64-w64-mingw32-dlltool -d lib/vulkan-1.def \
-l lib/libvulkan-1.a
cd build
../configure -q --host=x86_64-w64-mingw32 --disable-tests \
--disable-demos WIDL=x86_64-w64-mingw32-widl \
CPPFLAGS="-I$v/inc" LDFLAGS="-L$v/lib"
make -j$(nproc) > /dev/null
cp .libs/libvkd3d-utils-1.dll "$GITHUB_WORKSPACE/D3DCompiler_47.dll"
cp .libs/libvkd3d-shader-1.dll .libs/libvkd3d-1.dll \
"$GITHUB_WORKSPACE/"
- name: Build retroarch.exe with mingw-w64 (D3D8, D3D9, D3D10, D3D11, D3D12, slang)
shell: bash
run: |
set -eu
export CCACHE_DIR="$HOME/.ccache"
ccache -M 250M
# The screens' windows too, which video_screens_test runs.
./configure --host=x86_64-w64-mingw32 \
--disable-qt --disable-vulkan --disable-ffmpeg \
--disable-cheevos --disable-discord --enable-d3d8 \
--disable-sdl --disable-sdl2 --enable-video_windows
make -j$(nproc) CC="ccache x86_64-w64-mingw32-gcc" \
CXX="ccache x86_64-w64-mingw32-g++"
ccache -s
# The C and C++ runtimes it was linked against
for f in libstdc++-6.dll libgcc_s_seh-1.dll; do
cp "$(ls /usr/lib/gcc/x86_64-w64-mingw32/*-win32/$f | head -n 1)" .
done
make -C samples/cores/video_views DLL=1 CC=x86_64-w64-mingw32-gcc
- name: Build and run video_screens_test under Wine
shell: bash
run: |
set -eu
WINEDEBUG=-all xvfb-run -a python3 samples/settings/video_screens/build.py \
--output-dir "$RUNNER_TEMP/video-screens" --runner wine
- name: Views on GDI, D3D8, D3D9, D3D10, D3D11 and D3D12, end to end (Wine, Xvfb, llvmpipe, lavapipe)
shell: bash
run: |
set -eu
trap 'wineserver -k >/dev/null 2>&1 || true' EXIT
export LIBGL_ALWAYS_SOFTWARE=1
export VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.json
# Initialize the prefix with a display and wait for setup to finish.
WINEDEBUG=-all timeout 120 xvfb-run -a sh -c 'wineboot -i && wineserver -w'
RA_E2E_WINE=1 python3 samples/cores/video_views/e2e/run.py \
./retroarch.exe "$RUNNER_TEMP/views-e2e" gdi d3d8 d3d9_hlsl d3d10 d3d11 d3d12
# And d3d9_hlsl on the device of a system without Direct3D9Ex
RETROARCH_D3D9EX=0 RA_E2E_WINE=1 python3 samples/cores/video_views/e2e/run.py \
./retroarch.exe "$RUNNER_TEMP/views-e2e-legacy" d3d9_hlsl
- name: Preserve views logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: windows-wine-views-logs
path: ${{ runner.temp }}/views-e2e/**/*.log
if-no-files-found: ignore
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+46 -2
View File
@@ -3,7 +3,23 @@ name: CI Windows x64 (MXE)
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Windows-x64-MXE.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/Windows-x64-MXE.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
@@ -23,14 +39,42 @@ jobs:
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-mxe-win64-cross:gcc16
options: --user root
options: --user root --dns 1.1.1.1 --dns 8.8.8.8
steps:
- uses: taiki-e/checkout-action@v1
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Compile RA
run: |
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
ccache -M 250M
ccache -z
export MOC=/usr/lib/mxe/usr/x86_64-w64-mingw32.shared/qt5/bin/moc
./configure --host=x86_64-w64-mingw32.shared
CC="ccache x86_64-w64-mingw32.shared-gcc" \
CXX="ccache x86_64-w64-mingw32.shared-g++" \
./configure --host=x86_64-w64-mingw32.shared
make clean
make -j$(getconf _NPROCESSORS_ONLN)
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
+3
View File
@@ -27,6 +27,9 @@ jobs:
python-version: '3.10'
- name: Checkout
uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Crowdin Sync
shell: bash
env:
+3
View File
@@ -29,6 +29,9 @@ jobs:
python-version: '3.10'
- name: Checkout
uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Crowdin Sync
shell: bash
env:
+101
View File
@@ -0,0 +1,101 @@
name: CI iOS 6 (armv7)
# The iOS 6 build - RetroArch_iOS6.xcodeproj's defines, ARC, armv7,
# a 6.0 deployment target - compiled against the iOS 9.3 SDK, the last
# that takes a 6.0 floor. No runner Xcode can build armv7 or anything
# below iOS 12 any more, so this runs clang on Linux against the SDK's
# headers, as the PowerPC leg does for old macOS. Every warning is an
# error: anything newer than iOS 6 has to be behind a runtime check,
# and -Wunguarded-availability is what says so.
#
# The checking is .github/workflows/scripts/ios-legacy-check.sh, which
# runs by hand the same way; this job fetches the SDK headers for it.
#
# SDK: theos/sdks, pinned to a commit; only the headers are checked out.
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/iOS-Legacy.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!.github/workflows/*.yml'
- '.github/workflows/iOS-Legacy.yml'
# ci_paths: end
repository_dispatch:
types: [run_build]
permissions:
contents: read
env:
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
SDKS_COMMIT: 0222fd5413cf4b9af096f37b4621afa2688572f7
SDK: iPhoneOS9.3.sdk
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
build:
runs-on: ubuntu-24.04
env:
APT_PKGS: clang-18
timeout-minutes: 30
steps:
- uses: actions/checkout@v5
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install clang
timeout-minutes: 10
run: |
sh .github/workflows/scripts/apt-install.sh $APT_PKGS
clang-18 --version | head -n 1
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Fetch the iOS SDK headers
run: |
set -e
git init -q "$RUNNER_TEMP/sdks"
cd "$RUNNER_TEMP/sdks"
git remote add origin https://github.com/theos/sdks.git
git config core.sparseCheckout true
printf '%s\n' \
"/$SDK/usr/include/" \
"/$SDK/System/Library/Frameworks/*/Headers/" \
"/$SDK/System/Library/Frameworks/*/Frameworks/*.framework/Headers/" \
> .git/info/sparse-checkout
git fetch -q --depth 1 --filter=blob:none origin "$SDKS_COMMIT"
git checkout -q FETCH_HEAD
du -sh "$SDK"
- name: iOS 6.0
run: CC=clang-18 .github/workflows/scripts/ios-legacy-check.sh "$RUNNER_TEMP/sdks/$SDK" 6.0
+16
View File
@@ -3,7 +3,23 @@ name: CI iOS (13)
on:
push:
branches: [master]
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/iOS.yml'
# ci_paths: end
pull_request:
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/iOS.yml'
# ci_paths: end
permissions:
contents: read
+6 -2
View File
@@ -73,11 +73,11 @@ jobs:
working-directory: libretro-common
run: |
set -e
cc -std=gnu99 -O1 -g -Wall -DHAVE_THREADS -Iinclude \
cc -std=gnu99 -O1 -g1 -Wall -DHAVE_THREADS -Iinclude \
samples/rthreads/retro_procbarrier_test/retro_procbarrier_test.c \
rthreads/retro_procbarrier.c rthreads/rthreads.c \
-o retro_procbarrier_test ${{ matrix.libs }}
cc -std=gnu99 -O1 -g -Wall -DHAVE_THREADS -Iinclude \
cc -std=gnu99 -O1 -g1 -Wall -DHAVE_THREADS -Iinclude \
samples/rthreads/retro_asym_eventcount_test/retro_asym_eventcount_test.c \
rthreads/retro_asym_eventcount.c \
rthreads/retro_procbarrier.c rthreads/rthreads.c \
@@ -129,6 +129,10 @@ jobs:
rthreads\retro_procbarrier.c rthreads\rthreads.c ^
/Fe:retro_asym_eventcount_test.exe
if errorlevel 1 exit /b 1
cl /nologo /W3 /we4013 /O1 /TC /Iinclude ^
/DWINAPI_FAMILY=WINAPI_FAMILY_APP /D_WIN32_WINNT=0x0A00 ^
/c rthreads\retro_procbarrier.c /Fo:retro_procbarrier_uwp.obj
if errorlevel 1 exit /b 1
- name: Run every tier
shell: bash
+44 -39
View File
@@ -6,9 +6,25 @@ on:
push:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/retroarch.yml'
# ci_paths: end
pull_request:
branches:
- master
# ci_paths: generated by tools/ci_paths.py, do not edit
paths:
- '**'
- '!samples/**'
- '!libretro-common/samples/**'
- '!.github/workflows/*.yml'
- '.github/workflows/retroarch.yml'
# ci_paths: end
permissions:
contents: read
@@ -23,48 +39,37 @@ concurrency:
jobs:
linux-c89: # Smoketest build using most restrictive compiler and default options
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential libxkbcommon-dev libx11-xcb-dev libvulkan-dev libpipewire-0.3-dev zlib1g-dev libfreetype-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev nvidia-cg-toolkit nvidia-cg-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm
timeout-minutes: 30
steps:
- name: Dependencies
timeout-minutes: 20
run: |
# libvulkan-dev brings the Vulkan driver, the slang shader stack
# (shader_vulkan.c, slang_process.c, glslang_util.c, slang_cache.c)
# and the Vulkan contexts into this lane; without it configure
# drops them and none of those C files ever meet -std=c89.
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev libvulkan-dev zlib1g-dev libfreetype6-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev nvidia-cg-toolkit nvidia-cg-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm"
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "All packages already present on runner image; skipping apt."
exit 0
fi
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
echo "apt-get update failed (attempt $i); retrying in 10s"
sleep 10
done
# Fetch first, under a bound: a download is safe to stop and
# resumes, an install is not. A mirror that stalls is left for
# the main archive on the next attempt.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs && break
[ $i = 3 ] && exit 1
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
[ -f "$f" ] || continue
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
done
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
done
sudo apt-get install -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
$pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Configure
run: ./configure
- name: Build
@@ -72,4 +77,4 @@ jobs:
- name: Build Debug
run: |
make clean # making sure we don't have leftovers from previous build
make DEBUG=1 GL_DEBUG=1 C89_BUILD=1 info all
CFLAGS="-O0 -g1" CXXFLAGS="-O0 -g1" make DEBUG=1 GL_DEBUG=1 C89_BUILD=1 info all
@@ -0,0 +1,20 @@
#!/bin/sh
# The actions/cache key for apt-install.sh's cache: the packages asked
# for and the runner image. Jobs asking for the same packages share an
# entry, so the repository's cache holds each set once. A new runner
# image makes a new entry, seeded through the prefix by the last one's;
# apt-install.sh refreshes what no longer resolves.
# Off x86-64 the key also names the runner's architecture.
#
# apt-cache-key.sh "PACKAGE..." (writes key= and prefix= to
# GITHUB_OUTPUT)
set -eu
pkgs=$(printf '%s\n' $1 | sort -u | tr '\n' ' ')
hash=$(printf '%s' "$pkgs" | sha256sum | cut -c1-16)
arch=
[ "${RUNNER_ARCH:-X64}" = X64 ] || arch="$RUNNER_ARCH-"
prefix="apt-${ImageOS:-unknown}-$arch$hash-"
key="$prefix${ImageVersion:-unknown}"
echo "key=$key" >> "$GITHUB_OUTPUT"
echo "prefix=$prefix" >> "$GITHUB_OUTPUT"
echo "apt cache key: $key"
Loaded 100 of 11082 files, more files were not shown because too many files have changed in this diff. Show more