Compare commits

...
Author SHA1 Message Date
Codex 249ba6f2e3 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:46:44 +02:00
Codex 59e6caa7bf 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:27:01 +02:00
Codex 7169fb191d 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:27:00 +02:00
Codex 37c2964a10 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:26:35 +02:00
Codex ec8e234dc3 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 01:58:22 +02:00
Codex f817e27bab 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 01:46:49 +02:00
Codex 704ba2c4a1 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 01:45:26 +02:00
Codex fafc841034 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline
# Conflicts:
#	.github/workflows/Linux-menu-media.yml
#	menu/menu_media_browser.c
#	menu/menu_media_browser.h
#	samples/menu/rgui_media/rgui_media_test.c
#	samples/menu/rgui_media/run.py
2026-10-24 01:09:33 +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
Codex 28912bfb19 media: disable the legacy imageviewer core in normal builds
Keep image browsing, settings and history available through the menu viewer without HAVE_IMAGEVIEWER. Build extension filters from the enabled image codecs instead of querying the legacy core. Preserve its sources and Griffin guard.
2026-10-24 00:55:53 +02:00
Codex 19b2ac1e53 media: own fullscreen refresh requests through the menu lifecycle
Add an opt-in Match Display Refresh Rate setting shared by all four menus. Keep one temporary display owner, release it on source/viewer/settings changes and display-server teardown, and respect CRT SwitchRes and SDL mode-server selection. Stable frames make no mode queries; output notifications validate ownership before rebinding.

Retain an applied mode across compatible cadence changes instead of cycling the display. Core AV information remains unchanged. Win32 backend support is connected, but X11 and other relevant backend parity, precise fractional inventory, physical acceptance and VRR remain incomplete; keep this development feature off master.

GCC/Clang ASan+UBSan threaded/threadless controls, TSan, Win32 backend profiles and native Windows models pass. Full Linux C89 and native Windows frontends link. Real four-menu source/settings lifecycle, settings reference/unity/capacity/order and MSVC6 standin pass. No physical mode switch or completed VRR acceptance is claimed.
2026-10-24 00:42:20 +02:00
Codex 1215f526b9 media: add temporary Win32 refresh ownership callbacks
Select an advertised progressive mode at the captured geometry and depth using the shared cadence policy. Keep a separate snapshot for temporary playback ownership so release returns to the immediately preceding mode, including a paused core's mode, while preserving later external changes.

The backend and optional display-server callbacks are implemented; menu lifecycle ownership and settings are not connected yet. Keep this development checkpoint off master until integration acceptance.

C89 legacy/current ANSI/Unicode models pass GCC/Clang sanitizers, Wine and native Windows, covering matching/current/unsupported modes, geometry/depth/interlace filters, failures and conditional release. Real Windows-header ANSI/Unicode builds and read-only queries pass; physical playback refresh and VRR remain unaccepted.
2026-10-24 00:31:03 +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 8b084b8f02 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-24 00:26:15 +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 a37d9e5025 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:25:50 +02:00
Codex 7c5a1e511c Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-24 00:21:34 +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 5c91c6a297 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:14 +02:00
Codex e13540d39b Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-24 00:18:01 +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 cf5a318410 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:38 +02:00
Codex 6bc9be5d67 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-24 00:14:24 +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 4781239614 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:13:48 +02:00
Codex e3a48f3a16 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-24 00:09:59 +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 14a0a9102f 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:08:47 +02:00
Codex eefe17dd5b 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:08:17 +02:00
Codex 60de19825c Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 23:54:33 +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
Codex c0657f9b9f Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 23:50:23 +02:00
Codex 67d9d2e704 media: open external files in the fullscreen menu viewer
Prefer the internal viewer for supported movie and image arguments without an explicit core. Wait for content loading to finish before selecting the file in its browser directory. Keep unsupported formats, no-menu builds and explicit core requests on their existing paths.

Image confirmation now uses the fullscreen thumbnail viewer in all four menus and cannot launch an image-viewer core from that view. Add real startup/browser coverage and regenerate label fallbacks for menu-disabled builds.
2026-10-23 23:48:22 +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
Codex 1f5682f3bd media: offer fullscreen movie timing to CRT SwitchRes
Add a default-off Media Player option and publish dimensions and measured cadence to the existing display owner. Preserve core timing and hold a stable rate across seeks and timestamp quantization; withdraw the request when fullscreen closes or settings open.

Cover all four menu adapters, settings restoration, fractional rate changes, and concurrent numeric publication. Physical CRT scanout and desktop VRR acceptance remain separate gates.
2026-10-23 23:41:45 +02:00
Codex eb2c94efd7 vulkan: initialize published capabilities in import fixtures
Exercise the shipping capability publishers before checking imported formats. Cover immutable device support and revocation of the published format mask.
2026-10-23 23:19:55 +02:00
Codex a7961703e6 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:04:30 +02:00
Codex 0adb2302a9 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 22:50:23 +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
Codex 63ea9b5ae0 Batch file refills during builtin movie seeking
Expose the next MP4 packet span without reading media or advancing its cursor. Refill forward seek windows within the existing four-millisecond parser slice, capped at eight bounded reads, instead of yielding to the menu after each byte frontier. Preserve backward-frontier yields and decoder waits.

On the 4K60 AV1 sample, a rendered Windows Vulkan seek to 20:00.375 falls from 16.59 to 2.39 seconds. Repeated alternating seeks, all four rendered menu adapters, ASan/UBSan/TSan preview checks and codec regressions pass.
2026-10-23 22:22:20 +02:00
Codex f36c998b6c Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 21:50:40 +02:00
Codex a58bb21a15 Measure movie cadence from accepted presentation timestamps
Publish a bounded rational-rate estimate after 32 near-uniform intervals, tolerating millisecond timestamp quantization. Invalidate evidence on seeks, stopped playback and timing discontinuities. Carry measured cadence through shared CPU and texture playback state without querying decoder-owned data.

Cover variable timing, fractional and quantized rates, hardware backpressure and all four menu adapters. Display switching remains unchanged.
2026-10-23 21:49:59 +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
Codex a9b70a16aa Publish movie timestamps with presented player frames
Pass the existing decoder-owned timestamp through the threaded and inline presentation paths. Sinks can observe fractional movie timing without reading decoder state or inferring cadence from rounded frame durations. Cover leading gaps, loop epochs, unavailable timestamps and fractional intervals.
2026-10-23 21:37:59 +02:00
Codex 0a9d017609 Open media settings from fullscreen playback controls
Pause through the shared transport and open the existing Media Player settings list. Hide each menu viewer only after navigation succeeds and preserve browser return selection. Expose the action through mouse and existing keyboard/gamepad focus without adding input mappings.

Cover all four menus, failed pause/navigation, and threaded and inline playback.
2026-10-23 21:31:52 +02:00
Codex a9a6b925a8 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 21:20:18 +02:00
Codex 356d382c11 Add continuous folder playback to the builtin menu player
Keep a natural-order folder snapshot for opt-in continuous playback and shared previous/next file controls. Distinguish clean movie EOF from decoder failure and retain the final picture for transport actions. Apply repeat policy to video and mixer voices without restarting playback.

Cover all four menus, threaded and inline playback, folder boundaries, invalid next files, Stop and Back.
2026-10-23 21:19:18 +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 b2bd15fe7d Choose Android recording levels from picture rate and bitrate limits
Replace the fixed 1080p level split with AVC and HEVC picture-size, processing-rate, bitrate and dimension limits. Prefer HEVC main tier when it fits, retain explicit video_level values and refuse automatic requests beyond known limits. Native inventory and session admission remain mandatory.

Validate 1080p, 4K, 8K, fractional rates, bitrate boundaries, extreme aspect ratios and explicit overrides in both frontend profiles. Recording suites pass Windows GCC and Linux GCC/Clang ASan/UBSan; sixteen Android ABI/API/file-offset/history profiles compile under strict C89 and the 2048-byte stack ceiling. Physical device acceptance remains outstanding.
2026-10-23 18:27:43 +02:00
Codex 12f968b9fc Capture no-core menu recording from final video and mixed audio
Use full-window Vulkan menu capture with monotonic presentation timestamps and a bounded post-mixer PCM tap. Attach and retire the tap only through nonblocking cold operations on the existing audio owner lock; retain all I/O and GPU owners until independent retirement. Keep core AV information unchanged and Android native recording behind its explicit acceptance flag.

Validate the recording and shipping hook contracts on Windows and Linux, GCC/Clang ASan/UBSan and TSan, both NDK workflow matrices, object inventories and a default C89 frontend. Physical Android A/V and recovery acceptance remains open.
2026-10-23 18:20:56 +02:00
Codex 008b672bac Include Android recording objects in the source inventory contracts
The capture registry added a record-directory implementation include to Griffin. The inventory preprocessor only normalized gfx/common includes, so the JNI-contract CI tried to open the new file relative to stdin and failed before executing the remaining contracts.

Normalize every captured C implementation include into an object token, include the recording/frontend registry sections, and compare both real Makefile and Griffin inventories. Cover 24 valid codec/thread/recording profiles, require each selected object once, and verify that all eight threadless recording profiles are refused.

Validate the complete JNI-contract make check under GCC and Clang ASan/UBSan after reproducing the hosted failure. Production source, budgets and native capability admission are unchanged. The failing Clang job and cancelled GCC job from Android run 39980257395 are not passes.
2026-10-23 17:43:20 +02:00
Codex 4ef797f16d Publish Android recording history after successful native collection
Retain the original bounded output path in the cold frontend owner, independently of caller storage. Add it to video history exactly once after nonempty successful mux/file completion and GPU/I/O collection, with no session, file or system errors. Respect disabled or unavailable history and settings teardown; failed or cancelled output is never published.

Exercise history-enabled and history-free frontend profiles, deferred collection, caller filename mutation, path bounds, empty output, cancellation, native errors and missing settings/history. Compile the history branch explicitly in Android CI. All 23 suites pass Windows GCC C89 and Linux GCC/Clang ASan/UBSan; the native frontend passes 16 ABI/API/file-offset/history profiles and the exact new workflow command passes all eight history configurations. Native Android hardware acceptance remains open; ADB currently lists no connected device.
2026-10-23 17:36:28 +02:00
Codex 99c07ab0e0 Connect the Android recording frontend behind native acceptance opt-in
Wire the MediaCodec backend into viewport capture, PCM production, deferred startup, finalization and collection. Arm both clocks after render-owner acknowledgment, retain rational microsecond cadence independently of movie seeking, split bounded PCM packets without waiting, and fail rather than silently losing audio. Preserve PQ and convert scRGB once in the existing worker; unknown mastering metadata stays unknown.

Separate reset admission from full resource collection. A cold render-owner quiescence acknowledgment lets the existing Vulkan teardown drain prove ambiguous native work instead of deadlocking before that drain. It frees no native resources and grants no completion or device-death proof. Retain queued bindings, partial startup, GLES and I/O owners through collection; collect cancelled streams whose source end was already fixed by renderer failure.

Keep default registration off. Explicit HAVE_RECORD_MEDIACODEC builds require Android/Vulkan/threads/configuration support and link each native module once, including encoder-only device prerequisites. Test full frontend lifecycles, clocks, audio backpressure, output changes, queued closure and reset ownership with modeled platform boundaries.

Validate 22 suites on Windows GCC C89 and Linux GCC/Clang ASan/UBSan, reset guards, both eight-configuration NDK workflow groups including the enabled frontend and encoder-only variant, native registry compilation, actual Makefile profiles, C89 and ASan/UBSan shipping threaded-video harnesses, fresh CI frontend and unchanged hot/wait limits, MSVC6 stand-in and a full threadless frontend forcing 39 ownership APIs. Native hardware A/V/pixel/recovery acceptance, no-core player recording and final history integration remain open.
2026-10-23 17:25:58 +02:00
Codex 553d650782 Build Android capture configuration from enabled Vulkan device state
Resolve required native operations and copy memory properties into a stable cold controller. Require the enabled AHB/FOREIGN/SYNC_FD capability before admission; procedure availability alone cannot enable recording. Preserve the existing queue serialization contract and fd ownership without allocating native resources.

Publish immutable Vulkan device capabilities at initialization and use the existing atomic format cache for get_device, avoiding reads of mutable renderer flags and pipelines. Exercise the shipping getter concurrently and test resolver faults, admission, memory validation, submission and fd lifetimes.

Validate 21 recording suites on Windows GCC C89 and Linux GCC/Clang ASan/UBSan, getter ASan/UBSan/TSan and Wine profiles, both eight-configuration NDK workflow groups, full C89 threadless frontend with 38 forced ownership APIs, unchanged wait limits and MSVC6 unity stand-in. Actual Android recording hardware acceptance and frontend registration remain open.
2026-10-23 16:53:21 +02:00
Codex cd1963571c Marshal Android capture setup and collection through asynchronous render jobs
Reuse the shipping caller-owned upload queue for explicit cold operations, preserving opaque results through completion and teardown without treating them as textures. The Android capture request consumes this path for EGL/pool setup, service, closure, child preparation and collection. It uses the current Vulkan queue lock, drains queued binding references before closure, and retains stale-device owners until actual destruction proof.

Keep setup away from frame capture, copy cold memory properties, and guard unused GPU closure with the I/O claim. Validate Windows C89 and Linux sanitizer ownership suites, both Android NDK compile groups with final request rechecks, the real GL cold-job lane, TSan and ASan/UBSan shipping frontend lanes, full ASan/UBSan threaded harness, unchanged hot/wait budgets, MSVC6 stand-in and a full threadless link forcing 35 APIs. Recorder settings/audio-clock wiring and Android device acceptance remain open.
2026-10-23 16:22:18 +02:00
Codex a2744181aa Retire Android capture pools around actual Vulkan device destruction
Register each pool on its serialized render owner before native allocation, covering partial startup and recordings that have not published a frame. The existing Vulkan teardown drain supplies completion or device-loss proof. Prepare matching capture children before device destruction, refuse device caching while an owner remains, and notify source leases only after actual vkDestroyDevice. Failed closure freezes submission and service; unproven children retain the device.

Exercise matching and unrelated devices, partial preparation, failed drains, cache refusal, post-destruction callbacks and retained GLES reads. Validate 19 Windows/Linux recording suites, both NDK compile groups with final pool/registry rechecks in all eight configurations, and a full C89 threadless frontend forcing all 29 owner APIs. Android hardware recording acceptance and frontend registration remain open.
2026-10-23 15:45:56 +02:00
Codex cd33fb9062 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 15:42:38 +02:00
Codex 3af4ff288d Declare std::terminate through its owning exception header 2026-10-23 15:42:38 +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 a36a066959 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 15:30:36 +02:00
Codex 4f07a54e5e Prepare single GL context ownership during poke installation 2026-10-23 15:30:36 +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 23132fdf7d Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 15:21:22 +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
Codex b90e83ac00 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 15:11:10 +02:00
Codex 5e1a160d7f Connect Android capture pools to Vulkan frame submissions
Record final-image capture before presentation and report actual submission results under the existing queue synchronization. Bind stable pool leases, reject changed mode or extent, suppress duplicate timestamps and retain ownership on native errors. Expose the borrowed render queue for cold setup.
2026-10-23 15:11:09 +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 eb1c4c979e Retire capture Vulkan objects independently of GLES reads
Keep Android buffers and the GLES executor owned after Vulkan child retirement. Freeze relay service once device preparation begins so partial teardown cannot poll destroyed child objects or prematurely collect their shared buffers. Require actual device destruction and terminal GLES ownership before collection.
2026-10-23 14:44:52 +02:00
Codex 4beb10789e Merge commit 'cd005310ec2347c91092cc717e327cd705df30cb' into codex/builtin-media-baseline 2026-10-23 14:36:12 +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 8de9161c6e Merge commit '01a88010' into codex/builtin-media-baseline 2026-10-23 14:22:06 +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
Codex 8995fac08a Merge commit 'c0e4be82' into codex/builtin-media-baseline 2026-10-23 14:20:24 +02:00
Codex 3b1631163e 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:18:52 +02:00
Codex c0e4be8251 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:18:52 +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 4c3cf90d73 Merge commit '255f4be8' into codex/builtin-media-baseline
# Conflicts:
#	libretro-common/samples/formats/h265/Makefile
2026-10-23 14:03:48 +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
Codex fae6ebc8eb Route renderer capture ownership through the Android handoff
Keep uncommitted renderer work out of the encoder queue and expose record, commit and proven abort through the joint resource owner. Preserve unresolved source leases across cancellation so accepted work can still obtain completion proof without replay, and forbid native bookkeeping changes after device preparation.
2026-10-23 14:02:34 +02:00
Codex ffd49befc6 Record Android capture copies in renderer commands
Separate recording a capture copy from committing its completion after the renderer submits the frame. A dedicated empty command signals the existing exportable semaphore and fence; busy completion retries never replay the image copy. Retain source ownership until completion or explicit proof that an unsubmitted renderer command cannot execute.
2026-10-23 13:51:57 +02:00
Codex bd33b2ab28 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 13:43:54 +02:00
Codex 9f87fa089b Own Android recording EGL display and libraries
Hold a balanced Android system EGL initialization reference and keep both procedure libraries alive through recording collection. Select exact RGB8 or RGB10 recordable configurations without changing the controller context. Retain failed termination ownership without repeating a potentially consumed reference release.
2026-10-23 13:42:31 +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 a23020bbd5 Merge commit '4b4c0a8b' into codex/builtin-media-baseline 2026-10-23 13:41:39 +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 ccac4dc5b3 Load Android recording EGL and GLES operations atomically
Resolve the Surface, capture and draw procedures during cold setup. Missing procedures clear every operation table while preserving the borrowed display, buffers and format configuration, so startup cannot use a partially refreshed table. Capability checks remain with the native owners.
2026-10-23 13:35:49 +02:00
Codex 58363d892a Own Android capture resources through joint retirement
Construct the fixed capture images, Vulkan entries, GLES executor and handoff under one cold owner. Retain partial startup ownership, keep I/O borrowed, and collect only after copy/read retirement. Preserve two-phase Vulkan device teardown and source-only completion checks without adding frame-path allocation or waits.
2026-10-23 13:06:37 +02:00
Codex 12659387f1 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 12:47:57 +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
Codex d2b5462b87 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:15 +02:00
Codex 5668d9afc4 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 12:42:06 +02:00
Codex 52c80ab0be Expose completed capture source leases to the renderer
Provide a cached source-only completion check for renderer resource changes after production stops. Keep capture-buffer and device retirement separate, and verify multiple slots, reuse and outstanding GLES ownership.
2026-10-23 12:39:51 +02:00
Codex 6640500010 Retire original capture sources during open recording
Poll one unfinished copy per renderer service step even while the GLES consumer still owns its capture slot. Cache copy retirement separately from slot ownership so source leases do not remain pinned until slot reuse or recording closure.
2026-10-23 12:33:30 +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 ea35cd2973 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 12:28:25 +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
Codex c5fe577bcf 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:25:48 +02:00
Codex 6394afc9c3 Exercise capture relay with actual entry and slot queue
Cover direct and deferred submissions, cancellation before queued copy execution, normal worker drain and device-loss retirement using the shipping capture entry, deferred Vulkan queue and GPU slot queue. Mock only native Vulkan and executor boundaries.
2026-10-23 12:12:56 +02:00
Codex 2cbd669641 Relay Android Vulkan captures to the GLES executor
Retain deferred submissions without replay, transfer ready descriptors once, and require both Vulkan copy and GLES read retirement before slot reuse and final cleanup. Keep producer closure separate from GPU completion and preserve ambiguous submission ownership.
2026-10-23 12:07:27 +02:00
Codex 8b3296ee7f Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 11:58:48 +02:00
Codex 35899e65e4 Resolve GLES oracle recovery callbacks at runtime
Use the already loaded GLES runtime for recovery callbacks so the software oracle does not require a development-library linker symlink.
2026-10-23 11:53:56 +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 c4f0967df7 Recover recording reads with a terminal GLES fence
A missing slot fence can leave recording slots permanently held. Stop drawing and fence the same context stream once, then query completion without waiting. Retire source reads only after that fence signals; failures keep ownership and no frame is replayed.

Test fence creation/query faults, exact sync ownership and threadless execution. Run the shipping recovery on actual software GLES commands before pixel readback, with GCC and Clang and mandatory assertion checks.
2026-10-23 11:37:05 +02:00
Codex bcb51f41de Run Android recording GLES work on a separate executor
Create the recording Surface, fixed imports and draw resources on their own thread while codec output drains independently. Publish read retirement separately from Surface release and retain resources when completion cannot be proven.

Enforce one GPU executor per input window and collect it before the I/O owner. Validate dimensions and SDR/PQ configuration before startup. Exercise real slot queues, partial startup, cleanup retries and cancellation under sanitizers and TSan; extend native ABI and threadless checks.
2026-10-23 11:16:53 +02:00
Codex dbfcccfc6d Run Android recording I/O on an independent worker
Copy startup configuration and attach JNI on the I/O thread. Publish source ends and GPU retirement separately, coalesce nonblocking eventfd notifications, and keep native startup and finalization off producer threads.

Exercise concurrent submissions, closure during startup, cancellation, notification failures and thread faults. Compile the dispatcher in both native ABI/private-link groups; unsupported and threadless builds retain stubs.
2026-10-23 10:49:39 +02:00
Codex c473ac1470 Coordinate Android recording session ownership
Own encoders, PCM queue, mux and exclusive output on the independent I/O worker. Publish immutable producer ends separately from GPU read and Surface retirement; cancel without freeing resources still used by the GPU.

Cover startup rollback, delayed producer closure, ordered cancellation and file failures with a real PCM queue and concurrent producer notifications. Include the session in native ABI and private-link checks.
2026-10-23 10:31:29 +02:00
Codex e06385a2cc Separate Android encoder stop from GPU resource release
Allow terminal cancellation on the I/O owner while retaining the input window and codec until GPU retirement. Cache the native stop result, invalidate packet access and avoid repeating stop during final teardown.

Exercise held output, prior codec errors, EOS, stop failure and AAC cancellation with resource retention checks.
2026-10-23 10:20:03 +02:00
Codex c29a5ef860 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline
# Conflicts:
#	.github/workflows/Android.yml
2026-10-23 10:07:45 +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
Codex df2cfda5d8 Own fresh Android recording output descriptors
Create native paths exclusively and validate fresh adopted descriptors before transferring ownership. Preserve existing files and caller descriptors on refusal, and perform final sync and one-shot close only on the cold I/O owner.
2026-10-23 09:57:21 +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 cf0dd22df8 Configure and verify Android encoder color metadata
Match hardware admission and native configuration for explicit profile/level, SDR or HDR10 color aspects and static HDR metadata. Copy managed metadata ownership and reject contradictory output formats before muxing. Leave transfer conversion in the capture shader and do not request codec tone mapping.
2026-10-23 09:28:09 +02:00
Codex 5460adac99 Service Android capture frames through the GLES worker
Connect the fixed capture queue to readiness import, drawing, source-read fences and Surface posts. Hold queue slots through GPU retirement and refuse encoder EOS until closed input fully retires. Preserve partial Surface cleanup ownership and permit terminal context scopes after posting failures.
2026-10-23 09:10:45 +02:00
Codex dacf2d7a9f Draw Android recording RGB inputs with explicit transfer modes
Preserve encoded SDR and PQ values, and convert linear scRGB to PQ once using the existing capture primaries and luminance scale. Validate shader output independently on software GLES across native source formats and both orientations, alongside portable ownership and failure tests.
2026-10-23 09:00:08 +02:00
Codex f180af2c5a Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline
# Conflicts:
#	.github/workflows/Android.yml
2026-10-23 08:51:00 +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 e3cc552c34 Import fixed Android recording textures with GPU retirement
Keep readiness-fd consumption separate from completed texture reads. Retain partial cleanup ownership and refuse capture-slot reuse until a zero-timeout completion query succeeds.
2026-10-23 08:46:23 +02:00
Codex 6c35eb1b64 Submit Android capture copies with retained GPU source leases
Record fixed-slot Vulkan copies and foreign ownership transfers without warm allocation or CPU waits. Retain source leases through copy completion, transfer readiness fds once, and prevent binary semaphore reuse after an unconsumed export failure.

Separate child-object destruction from final source/native-buffer retirement so device teardown remains ordered. Exercise direct and deferred submission, cancellation, device loss and repeated reuse with the real deferred queue. Keep mock EGL tests independent of X11 headers.
2026-10-23 08:29:15 +02:00
Codex 781853a905 Allocate fixed Android RGB capture image pools
Query native RGB external-memory import and usage support before allocating AHardwareBuffers. Own dedicated Vulkan images and memory for RGB8, RGB10 and FP16 capture, preserve optimal native usage flags, and unwind partial resources without CPU mapping.

Exercise allocation faults, buffer identity and reference ownership with portable mocks, and compile standalone and combined Android recording units across both ABIs and file-offset modes. GPU readiness, foreign ownership transitions and worker reads remain with the capture producer/worker integration.
2026-10-23 07:50:13 +02:00
Codex 78cc0196d9 Own worker EGL surfaces and exact encoder presentation times
Create a validated recordable ES3 context and encoder window surface on the recording worker. Preserve zero-based source PTS, restore prior GLES bindings after posting, and retain failed restores or native destruction for explicit retry. Accept promoted EGL 1.5 context/colorspace support and require explicit PQ output capability.

Exercise context, format, timestamp and cleanup faults with portable mocks; compile the Surface and combined recording unit against every Android SDK ABI/file-offset configuration. GPU capture and recording registration remain separate work.
2026-10-23 07:30:58 +02:00
Codex 766a365f61 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 07:17:59 +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
Codex 01a54a9fd2 Coordinate Android video, AAC and MP4 worker ownership
Drain both encoders without losing borrowed packets while mux tracks start. Consume PCM incrementally across backpressure and finalize tracks with explicit source ends only after encoder drain. Exercise real PCM queue ownership with delayed codec formats and failures, and link the worker dependencies in the Android SDK matrix.
2026-10-23 07:10:54 +02:00
Codex d09da7b20e Link threaded codec dependencies in the AV1 Metal fixture
Build the thread pool and event count before the first Metal fixture link, including when native AV1 decoding is unavailable.
2026-10-23 07:04:23 +02:00
Codex dd38c0dab3 Encode worker-owned AAC PCM with sample-clock timestamps
Reuse encoded-output ownership for platform AAC-LC input. Submit one bounded native buffer at zero timeout, retain unconsumed PCM across backpressure, fill sample-clock gaps with bounded silence and fix EOS before retries. Validate actual output rate/channels and preserve the Android queue offset ABI.
2026-10-23 07:00:03 +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
Codex 2b12a040c2 Own Android MP4 muxing and explicit track end timestamps
Bind native video/AAC formats before start, borrow compressed packets only during worker writes, and preserve source timestamps and encoder order. Require explicit source end markers before finalization and keep native errors sticky. Extend portable fault tests and NDK ABI/unity checks; recording frontend and device output acceptance remain open.
2026-10-23 06:45:07 +02:00
Codex 6e28729120 Own Android Surface encoding and borrowed output on the worker
Create an explicitly admitted native encoder and input Surface with ordered partial cleanup. Pump one zero-timeout output action, retain packet borrows until acknowledgement, and preserve format/EOS/error ownership. Add portable fault tests and native NDK ABI/unity compilation; GPU capture and mux integration remain separate.
2026-10-23 06:35:34 +02:00
Codex b71ce6e61d Require libaom in macOS AV1 acceptance tooling
Use the Homebrew full FFmpeg formula when the current binary lacks libaom-av1. Check the selected encoder before generating grain and chroma fixtures.
2026-10-23 06:31:19 +02:00
Codex f5acdc1722 Query complete Android Surface encoder input formats
Add cold hardware admission for bitrate, fractional frame rate and COLOR_FormatSurface alongside profile and dimensions. Preserve decoder selection and exception ownership; exercise encoder filtering, rejection and local-reference bounds through real JNI.
2026-10-23 06:25:29 +02:00
Codex a56e1b006a Compile AV1 native fixtures when hardware is unavailable
Keep unavailable decoder probes distinct from native SDK and linker validation. Build Metal and container fixtures even after an unavailable probe, while preserving compiler failures and exit 77.
2026-10-23 06:21:53 +02:00
Codex f901aa40d4 Keep Darwin loader declarations visible in AV1 acceptance
Remove the unnecessary POSIX feature restriction, which hides RTLD_DEFAULT on Apple and prevents the native AV1 fixture from compiling.
2026-10-23 06:18:53 +02:00
Codex 93b3114029 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 06:14:21 +02:00
Codex e0c48b473d Exercise native AV1 MP4 and WebM Metal playback
Check independent decoded-plane and FrameView pixels through container streams, alternating warm seeks, reopen and retained pool exports. Require one native session per stream and explicit native completion; portable probes report unavailable.
2026-10-23 06:13:38 +02:00
LibretroAdmin 3aca0fdf94 gl3_shader_status: stub glslang_compile_shader_text 2026-10-23 06:11:01 +02:00
Codex 33996131a1 Release the AV1 fixture lease before Metal submission
Avoid an extra fixture reference masking consumer ownership during asynchronous GPU completion. Check that completion has not advanced before submitting the command buffer.
2026-10-23 06:07:22 +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
Codex 44d4e9c5ac Exercise AV1 decoder exports through native Metal movie conversion
Use the published backend, retained pool leases and shipping FrameView path for non-square decoded images. Check GPU plane hashes and independent chroma-aware RGB references across warm seek, renderer teardown and reopen. Keep raw decode and Metal acceptance modes distinct.
2026-10-23 05:58:50 +02:00
Codex 54b9271633 Distinguish AV1 native setup failures from unavailable hardware
Only known decoder availability errors return 77. Require the native completion marker before recording a successful device case, so an empty-success executable cannot produce a false acceptance result.
2026-10-23 05:40:50 +02:00
Codex 51dce116c6 Add native VideoToolbox AV1 pixel and seek acceptance
Compare eight generated 8/10-bit chroma/grain cases with independent decoded references. Require hardware decoding, preserve a native image across warm keyframe seek and retirement, and report unavailable devices separately from passes. Metal import acceptance remains separate.
2026-10-23 05:37:51 +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 2f7e664d88 Publish AV1 decoding through the shared Metal lifecycle
Use the retained renderer snapshot for AV1 startup and withdraw only its offer on matching renderer teardown. Extend native lifecycle checks across AV1-only and combined-codec builds in both ARC modes.
2026-10-23 05:28:40 +02:00
Codex 599c35c247 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 05:24:55 +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 731d95f52c Add serialized AV1 dispatch and Metal backend admission
Follow the existing VP9 executor pattern with preallocated signal/timer sources and deferred retirement. Admit AV1 using retained renderer snapshots, bounded session storage, supported conversion metadata and current device generations.
2026-10-23 05:20:23 +02:00
Codex 823de371e8 Carry explicit AV1 chroma placement through Metal conversion
Keep placement in private leases and reconstruct explicit vertical/top-left chroma with bounded taps. Preserve legacy unspecified sampling and core HDR ABI. Extend native HDR references and macOS AV1 configuration/ownership checks; native execution remains pending.
2026-10-23 05:15:10 +02:00
Codex 8c5e4473cc Adapt AV1 owner output to retained Metal frame leases
Snapshot accepted display dimensions and grain parameters, preserve images on export backpressure, and reuse native sessions on restart. Exercise sequence mismatch refusal, show-existing output and leases held through decoder retirement with the mock pool.
2026-10-23 05:11:04 +02:00
Codex bdad7a1acd Own bounded VideoToolbox AV1 service and completion slots
Follow the VP9 queue and retirement pattern for staged AV1 samples. Retain native output independently of owner shutdown and reject stale callback cookies before marking errors. Cover backpressure, hidden drain, restart, delayed release and concurrent callback publication.
2026-10-23 05:08:24 +02:00
Codex ce6d74fc2d Stage bounded AV1 samples for VideoToolbox ownership
Normalize sized OBUs into preallocated packet storage, seal hidden/display batches, and retain borrowed bytes through the CoreMedia release callback. Exercise busy retries, overlapping input, allocation errors and delayed native sample release.
2026-10-23 05:04:27 +02:00
Codex 151a67d06d Keep AV1 sample presentation delay unspecified in Android setup
The sequence display delay counts frames; av1C presentation delay counts samples and needs container grouping information. Preserve the coded sequence delay without copying it into the configuration prefix.
2026-10-23 05:00:27 +02:00
Codex bfd3f884d0 Add cold VideoToolbox AV1 format and session setup
Validate the sequence and container color before building av1C and requesting a hardware-only Metal-compatible session. Keep sample presentation delay unspecified when only frame display delay is known.

Exercise portable configuration, setup allocation failures and partial native creation errors. Native Apple format execution and decoder integration remain pending.
2026-10-23 04:58:11 +02:00
Codex d355ae237b Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 04:42:49 +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 e89a508e9a Add VA AV1 native pixel and seek acceptance runner
Generate 8/10-bit streams and independent FFmpeg pixel references for chroma placement and film grain. Exercise keyframe seek/reopen cycles, retained exports, native Vulkan import and owner retirement.

Keep portable fixture checks distinct from native acceptance, reject mismatched binary modes, and report unavailable hardware without counting a pass. Extend the shared import oracle to validate chroma placement.
2026-10-23 04:39:43 +02:00
Codex 7e8587cbcf Carry AV1 chroma placement through VA Vulkan conversion
Preserve signaled vertical and colocated chroma positions in private frame leases and reconstruct sampled planes at their specified locations. Clamp interpolation to the coded extent so allocation padding cannot bleed into the picture. Unspecified positions retain existing sampling; reserved values refuse.

Validate metadata lifetime and admission, 8/10-bit SDR/PQ/HLG GPU pixels, padding/crop boundaries, and a negative control that drops placement.
2026-10-23 04:20:52 +02:00
Codex 12d11e8322 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 03:59:42 +02:00
Codex e736b5aa75 Merge commit 'bf777848c63eee8dd84f5bb94df2245d221d3038' into codex/builtin-media-baseline 2026-10-23 03:47:19 +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
Codex 7cce0be1a2 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:46:30 +02:00
Codex d2a2fdc37e Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 03:34:47 +02:00
Codex ab554a3ab7 Offer VA AV1 decoding on eligible Vulkan devices
Keep native Vulkan Video AV1 preferred and publish VA only for the actual DRM PRIME import device. Reserve 32 physical surfaces independently of the nine logical stream slots, bound cold sessions and defer native retirement. Detect AV1 SDK headers separately so absent headers leave HEVC and VP9 available. Exercise admission, epoch changes, retirement, threadless refusal and notification lifetime under sanitizers and TSan.
2026-10-23 03:34:17 +02:00
Codex 54c3407bb0 Own native VA AV1 startup and deferred retirement
Combine AV1 runtime, physical pool and decoder lifetimes without terminating while renderer exports remain owned. Keep partial startup retireable and retry termination without repeating native cleanup. Share source color admission with the decoder and cover 8/10-bit runtime faults, retained grain exports and nine-surface non-grain admission.
2026-10-23 03:22:44 +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
Codex 6695043ce9 Map VA AV1 references and retained grain outputs
Keep physical reference and grain display pairs for each logical AV1 slot. Preserve cached display images across inter frames and exported leases across logical reuse, with bounded BUSY and identical-packet retry when surfaces are held. Snapshot output geometry, grain and source color metadata, and defer decoder retirement until renderer ownership returns. Explicit chroma placement still refuses pending renderer support.
2026-10-23 03:15:57 +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
Codex d7182ccea7 Export completed VA AV1 display surfaces
Reuse the shared DMA-BUF descriptor and opaque frame ownership path with AV1 pool generation checks. Cover 8/10-bit grain display export, pending completion refusal, retained renderer ownership, close retries and duplicate native descriptors.
2026-10-23 03:04:15 +02:00
Codex 6ddc16eff2 Protect AV1 VA reference and grain surface ownership
Track native reads and both write destinations through bounded completion and retirement. Retained exports use incarnation tickets, and grained output cannot become a reference. Allocate picture and tile work storage with the cold pool instead of on the frame stack.
2026-10-23 03:00:42 +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
Codex 1dbb4ecb04 Track both VA AV1 submission destinations
Submit bounded typed picture and tile buffers with the shared VA transaction operations. Poll reference and grain display surfaces without waiting, retain uncertain work until display termination, and retry failed buffer destruction. Validate surface membership, tile spans and session geometry before native ownership.
2026-10-23 02:56:57 +02:00
Codex 2dbc25edaf Build native VA AV1 picture parameters
Map effective AV1 references, filters, restoration, global motion and film grain into typed VA buffers. Reject malformed state without changing caller output and keep separate grain display ownership explicit. Cover parsed 8/10-bit fixtures, reference mapping and bounded refusal without claiming native hardware decode.
2026-10-23 02:52:10 +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
Codex 2f26f56198 Build bounded native VA AV1 tile records
Translate validated whole-frame tile spans into SDK-native slice records using caller-owned storage. Recheck every span before publishing output and preserve output on refusal. Validate reserved fields and maximum capacity, and compare native offsets and sizes against independent AV1 traces on real coded streams.
2026-10-23 02:39:26 +02:00
Codex 5e5ba8b90c Merge master Exynos and CI package-cache updates 2026-10-23 02:22:48 +02:00
Codex 9a697765f8 Add VA AV1 session admission and runtime ownership
Admit AV1 profile 0 with 8/10-bit render formats and nine to thirty-two surfaces through the existing native VA lifecycle. Preserve partial initialization and retryable cleanup, and refuse AV1 before native ownership on older SDKs. Cover startup faults, boundaries and compatibility without registering an incomplete decoder.
2026-10-23 02:21:55 +02:00
Codex 89364ba943 Keep retained VA VP9 surfaces independent of logical slots
Map logical DPB slots to native surfaces on accepted decode, including references, previous pictures and output. Select an unheld surface when a logical slot is reused so retained renderer exports cannot stall otherwise available decode capacity. Cover remapped output and references, full 32-surface backpressure, retry and stale ticket refusal.
2026-10-23 02:13:48 +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
Codex 37fb6dd13e Add native VA VP9 pixel and import acceptance runner
Check profile 0/2 decode against independent fixture hashes and retain an exported frame across later pictures and decoder detach. Reuse the Vulkan modifier import oracle for conversion and exact-device ownership checks. Build both native variants in the Linux sample lane; require an explicit render node to execute them and report unavailable without a device.
2026-10-23 02:02:35 +02:00
Codex 6fa9e490f0 Register VA VP9 decoding on eligible Vulkan devices
Publish the parsed VP9 backend only for matching PRIME import devices, preserving native Vulkan Video preference. Retire decoder contexts off the frame path and retain outstanding exports across renderer replacement. Add factory admission and lifetime coverage, plus the deferred queue dependency for the native import oracle.
2026-10-23 01:57:19 +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
Codex 6a9886de64 Merge master shader compiler changes 2026-10-23 01:41:16 +02:00
Codex ac35f9fa36 Keep VA queue inclusion outside the Android unity block
Preserve the Android source-matrix anchor while compiling the shared conversion queue for non-Android VA builds. The sixteen codec/thread configurations and renderer lifecycle variants pass locally.
2026-10-23 01:40:03 +02:00
Codex 6a6b492626 Build VA VP9 ownership and defer Vulkan conversion submits
Add profile 0/2 parameters, bounded submission, surface generations and decoder retirement using the existing VA runtime and opaque export path.

Prepare VA imports on the cold owner and drain conversions under existing renderer queue serialization. Preserve exports on deferred or ambiguous GPU use, and find the required libdrm headers independently of KMS.

Native VA playback acceptance and VP9 frontend registration remain pending.
2026-10-23 01:38:12 +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 b9392ebf46 Test VP9 WebM playback through the display API
WebM skip intentionally discards display output. Use next for the native playback oracle so its frame, pixel, seek and retained-lease assertions exercise exported frames.
2026-10-23 01:17:30 +02:00
Codex 20ebf595cd Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 00:35:07 +02:00
Codex a4b20df274 vulkan: collect retired Android imports during cold setup 2026-10-23 00:35:07 +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
Codex 7744736d83 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 00:29:26 +02:00
Codex ab25c98feb media: defer Android imports through the renderer submission queue
Drain conversions under the existing queue lock, retain queued leases until completion, and prepare native identities through owner resource work before warm imports.
2026-10-23 00:29:21 +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 de524e63d9 media: reconcile public Vulkan Android warm path 2026-10-23 00:05:13 +02:00
Codex 4613d47bc0 vulkan: make Android warm-path ownership checks portable
Run the positive-FD ownership and retirement cases on Windows CRT pipes while retaining real POSIX paths on Linux and Android. Add a portable warm-path runner, strict NDK coverage and project formatting without changing the production ownership algorithm.
2026-10-23 00:05:11 +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 f01298f246 media: reconcile public Vulkan Android resources 2026-10-22 23:54:11 +02:00
Codex 8465e993b4 vulkan: validate portable Android import resource contracts
Use portable model and pixel runners, execute embedded and fresh-source shader oracles, and extend strict Android ABI coverage. Apply the project style and distinguish legacy descriptor reservation from native admission.
2026-10-22 23:54:08 +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 2c21231c86 media: reconcile public Android color export 2026-10-22 23:44:09 +02:00
Codex 616b915dc3 android: align color export with shared VP9 metadata
Use the published libretro-common VP9 color resolver, add a portable three-codec export runner and extend Android ABI coverage. Apply the project style without changing the metadata or lease contract.
2026-10-22 23:44:06 +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 f8ee96bc96 media: reconcile public Android frame leases 2026-10-22 23:40:18 +02:00
Codex 266761837b android: make opaque frame lease checks portable
Use RetroArch threads on Windows and Unix, preserve native Linux fence checks and exercise actual stale generation reuse. Add a portable lease/HDR runner and Android ABI coverage with the project code style.
2026-10-22 23:40:14 +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 e8e2ff3396 media: reconcile public codec worker sessions 2026-10-22 23:36:06 +02:00
Codex fe199993a3 android: make worker session contracts portable
Add a temporary-output session runner for Windows and Unix, test codec-configuration output and timestamp overflow, and check session SDK signatures in the Android ABI matrix. Apply the project code style.
2026-10-22 23:35:38 +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 035e1b0a53 media: reconcile image ownership with current master 2026-10-22 23:31:33 +02:00
Codex be3e85bb5b android: reject empty native image acquisitions
Do not publish an image wrapper when the native API reports success without an AImage. Keep the reader fault test portable, apply the project style and give image ownership its own CI job.
2026-10-22 23:31:06 +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 95adc8c060 android: make image ownership tests portable
Use RetroArch threads for concurrent image returns and exercise no-fence lifecycle contracts on Windows while retaining native-fence polling tests on Linux. Apply the project style to the queue and its tests.
2026-10-22 23:28:30 +02:00
Codex 3cbfdbb91e android: make codec inventory contracts portable
Run the real JVM tests from temporary output directories on Windows and Unix, retain separate inventory CI coverage, and apply the project code style.
2026-10-22 23:25:03 +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 f89b4a8fb7 vp9: reuse native sessions across key-frame seeks
Append an optional nonblocking restart callback. Drain discarded work before resetting reference state, preserving exported leases and avoiding repeated VideoToolbox session creation. Cover pending restart, repeated seeks and restart failure.
2026-10-22 23:20:53 +02:00
Codex 8218c1923a media: reconcile hardware migration with current master
Preserve the current shader and CI layout and published backend prefixes while integrating the pending platform decoder work. Keep device acceptance on the candidate branch.
2026-10-22 23:05:02 +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 ff7cef3da1 av1: withdraw only the retiring backend offer 2026-10-22 22:52:36 +02:00
Codex 620405d736 test: distinguish VP9 reopen and output failures 2026-10-22 22:45:20 +02:00
Codex 1482dab102 av1: preserve the published backend prefix 2026-10-22 22:41:26 +02:00
Codex 4f5ae7450c media: add explicit play and pause actions 2026-10-22 22:33:14 +02:00
Codex b316823902 media: add explicit play and pause actions 2026-10-22 22:33:08 +02:00
Codex d89cec4bdf vp9: match published calloc argument ordering 2026-10-22 22:28:21 +02:00
Codex a93d2c4663 test: identify VP9 seek output refusal 2026-10-22 22:21:36 +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
Codex 9a22d549c1 test: guard immutable CoreFoundation mock values
Assert non-null retained values and return before atomic object refcounts for mock CF booleans. This preserves the real constant-ownership contract and avoids GCC15 treating sentinel booleans as possible objects. Windows64 and Linux sanitizer mock suites pass without disabling warnings.
2026-10-22 22:13:44 +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
Codex 9fd92b3a68 video: declare VP9 color properties to VideoToolbox
Supply primaries, transfer, matrix and full-range CoreMedia format extensions in addition to vpcC. Native run39842936896 decoded BT709 content into buffers tagged SMPTE-C/BT601, causing strict Metal import to reject the first frame. Preserve coded/container metadata validation; do not override output attachments or weaken import checks. Verify explicit SDR/sRGB/PQ/HLG and range properties plus unchanged setup failure cleanup.
2026-10-22 22:05:33 +02:00
Codex a35a2b36da vp9: correct Matroska vertical chroma mapping
Map left/half-row siting to VPCC vertical 4:2:0 and top-left to colocated 4:2:0. Reject horizontal-half descriptions that neither supported VPCC value represents. Fix the swapped-axis candidate logic and cover both valid locations plus horizontal-half software fallback against the published container/binding definitions.
2026-10-22 22:05:33 +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 300be42cea vp9: preserve replacement offers during renderer retirement
Use conditional registry withdrawal for Vulkan and D3D11 shutdown. Verify that a stale backend cannot withdraw a newer offer and that a subsequent open snapshots the replacement. This is cold lifecycle work; decoder frame processing is unchanged.
2026-10-22 21:52:35 +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 f1ddffb95a test: diagnose native VP9 CoreVideo export refusal
Instrument the extracted acceptance-only Metal export failure branch with native pixel format and borrowed color attachments. Require an exact shipping-source anchor; preserve production code and export return behavior.
2026-10-22 21:42:22 +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 1f53fc376c test: identify native VP9 stream output failures
Print the active container/color/repeat case and stopped output count before the existing strict assertion. Keep diagnostics in the acceptance fixture; do not change native decoder behavior or weaken expected output.
2026-10-22 21:37:02 +02:00
Codex 166835d878 vp9: preserve published backend prefix for color extensions
Keep user/open/close/packet_decoder offsets when appending color, drain and configuration callbacks. Update positional test offers and verify mode offsets plus callback append placement with C89 compile-time checks. This aligns the pending native backends with the incrementally landed master API.
2026-10-22 21:35:50 +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 a4ef3b5f4a 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:20 +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 6a2aa0b6e9 vp8: retain legacy MSVC support in stack helper
Match the existing VP9 noinline compiler guard: declspec(noinline) requires MSVC 1300 or newer. Older compilers keep the portable helper definition. Apple/GCC/Clang paths are unchanged.
2026-10-22 21:24:18 +02:00
Codex 725afac1f5 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:18:57 +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 5bb8d13270 test: cover hidden VP9 pictures and native repeat packets
Add valid hidden-keyframe/show-existing superframes to every MP4/WebM depth and color fixture. Independently verify with FFmpeg that visible output remains byte-identical. Carry the cases through native Metal plane/color, repeated-seek and retained-export acceptance. Host software pixel and seek checks pass; native execution remains pending.
2026-10-22 21:14:23 +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
Codex 1e1bc0d3e4 test: exercise native VP9 Metal playback and repeated seeks
Run the shipping Metal decoder factory, MP4/WebM streams and FrameView conversion against independent decoded-plane CRC and color references. Cover 8/10-bit SDR, PQ and HLG, alternating seeks and an exported frame retained through stream and pool close. Keep GPU waits and readbacks confined to acceptance fixtures; require native execution and report missing devices separately.
2026-10-22 21:08:30 +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 d74c08de2c test: execute native VP9 hardware pixels and retained output
Decode fixed lossless 8/10-bit pictures through the shipping owner and a separate serialized worker. Compare native planar output against FFmpeg-derived references, reopen sessions and retain decoded images through retirement. Report unavailable hardware explicitly without claiming pixel acceptance.
2026-10-22 20:34:29 +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
Codex 6d418fd6a5 ci: recover stalled VA oracle dependency downloads
Apply bounded package download and mirror recovery to the VA matrix job after its dependency installation exhausted the deadline before any tests ran. Preserve all test commands and reserve time beyond dependency recovery.
2026-10-22 20:22:47 +02:00
Codex 48c17098a6 mp4: connect VP9 hardware sessions to stream playback
Use configuration-aware cold admission and bounded superframe submission with retained input on BUSY. Preserve output leases, drain terminal packet decoders, and reopen from retained initial headers during seeks. Keep software fallback and reject coded color changes before native submission.
2026-10-22 20:22: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
Codex 4d9642f844 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 20:09:24 +02:00
Codex 4891a5de59 ci: recover stalled Metal mock dependency downloads
The Metal mock job exhausted its ten-minute deadline while installing packages, before any test ran. Use the established bounded download and mirror recovery recipe, and allow its existing twenty-minute dependency bound plus test execution. Preserve every test command.
2026-10-22 20:02:08 +02:00
Codex e02fc1a467 ci: budget graphics job for dependency recovery and full regressions
A healthy preview pass took 8m40s in the prior successful job. After mirror recovery, the next run reached that step with only 7m28s before its 30-minute job cutoff. Allow the existing 20-minute dependency bound plus the measured regression workload and setup overhead; preserve every test command and per-command timeout.
2026-10-22 19:31:14 +02:00
Codex ef4956ee82 vp9: carry container chroma through hardware admission
Add an append-only cold configuration callback while preserving legacy callback selection. Carry Matroska siting to VideoToolbox codec setup, reject truncated or unrepresentable descriptions, and retain resolved coded/container color for presentation. Share the existing renderer color policy with the stream layer without changing the core HDR ABI.
2026-10-22 19:30:27 +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
Codex ee5eed049b 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 13:44:54 +02:00
Codex 415735875e ci: bound shader dependency downloads and retry stalled mirrors
Use the existing formats-job package download and mirror fallback pattern in the two shader jobs that stalled before checkout. Fetch under a bound before installation, retain visible package output, and preserve all shader steps and the existing overall job deadline.
2026-10-22 13:39:49 +02:00
Codex b8b6fca0b2 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 13:36:52 +02:00
Codex 2891916ad7 tests: keep combined Metal codec declarations C89 compatible 2026-10-22 13:32:55 +02:00
Codex eea612586b metal: publish VP9 and HEVC decoder offers from shared lifecycle
Reuse the existing retained setup snapshot and lifecycle lock for both codecs. Keep VP9 independent of MP4 support, resolve optional macOS registration at setup, and preserve compare-withdrawal of foreign offers. Exercise native lifecycle builds with each codec and both ARC modes.
2026-10-22 13:29:26 +02:00
Codex c7622782cc video: share retained Metal decoder capability snapshots
Reuse the HEVC setup lock and publication ordering for device, HDR capability and nonwrapping generation snapshots. Keep owner queries lock-free and expose the same snapshot contract to VP9 without a HEVC dependency.
2026-10-22 13:10:45 +02:00
Codex f036a9c764 video: admit VP9 sessions with stable Metal capability snapshots
Build the packet-decoder backend around an owned device/HDR snapshot and validate its generation before and after native setup. Balance failed setup resources and retire stale sessions through the existing executor.
2026-10-22 12:52:17 +02:00
Codex c047f15028 video: schedule VP9 native work with preallocated dispatch sources
Use coalesced source wakes for serialized native submission and deferred retirement. Retain the close-notification source across concurrent cancellation and use a setup-time timer for outstanding native block retirement. Check native C, ARC and MRC SDK builds separately from host dispatch models.
2026-10-22 12:40:17 +02:00
Codex f90e2de655 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 12:26:38 +02:00
Codex bda2fbea3f video: export VideoToolbox VP9 output through Metal pools
Connect the VP9 packet-decoder contract to the bounded native owner and retain images across busy Metal exports. Discard superseded display results by generation so reused DPB slots cannot return stale pictures, while exported leases survive deferred retirement.
2026-10-22 12:24:37 +02:00
Codex e20ba583e4 video: connect bounded VideoToolbox VP9 owner service
Queue startup-owned compressed batches for serialized native submission, preserve final visible output and recycle storage only after submission and native block release. Close admission before deferred retirement and keep owners alive while native blocks remain retained.
2026-10-22 12:16:18 +02:00
Codex 9c3e97f739 video: guard VP9 completion slots against stale callbacks
Match callbacks with nonrepeating generation cookies and claim completion atomically. Transfer retained images to the owner and keep null-image success separate from visible output; retiring owners keep slot pools until native callbacks quiesce.
2026-10-22 12:04:08 +02:00
Codex 77ef1c7d95 video: pin VP9 sample storage through native block release
Create and submit CoreMedia samples on the serialized worker. Keep staged input pinned until CoreMedia releases its block, independently of decode callbacks, and suppress display output for hidden-only batches.
2026-10-22 11:57:00 +02:00
Codex 0f0ee60703 video: stage bounded VP9 batches for VideoToolbox
Own accepted packet bytes in startup-provided storage and emit Annex B indexes. Seal on display, capacity or terminal input, retaining unaccepted input for retry without growing buffers.
2026-10-22 11:51:52 +02:00
Codex bcc0d9bdcb video: create hardware-only VideoToolbox VP9 sessions
Build CoreMedia VP9 formats and request Metal-compatible IOSurface pools. Release partial format/session state on setup failures and keep supplemental decoder registration optional for older platforms.
2026-10-22 11:47:32 +02:00
Codex cbbf6d2a43 video: share VP9 color admission and build VideoToolbox configuration
Reuse the existing coded/container color intersection for Android and the new VideoToolbox configuration helper. Build the versioned vpcC payload for 8/10-bit 4:2:0 with explicit chroma position, unspecified level and preserved metadata. Reject reserved color fields, conflicts and unsupported output layouts rather than guessing HDR.

Add portable strict-C89 configuration conformance to Linux and macOS CI. GCC/Clang configuration and full MediaCodec sanitizers, Windows64 runtime and sixteen Android compile objects pass. Twenty measured frames remain below2048 bytes. Win32 compiles but Windows security blocked copying its executable, so Win32 runtime is not claimed. Native VideoToolbox session/packet/Metal integration remains separate work.
2026-10-22 11:34:57 +02:00
Donleon a81e450c00 docs: list ArmSoM CM5 Linux/AArch64 platform (#19900) 2026-10-22 11:26:43 +02:00
Codex 1d862204fa av1: retain admitted startup sequences for container reopen
Keep the actual sequence bytes after compatible pre-frame admission changes, so MP4 and WebM seeks reopen with the accepted decoding flags even when original packet input is temporarily unavailable. Bound owned configuration to 1 MiB, reuse identical storage, and free it with the outer stream. Retention runs only during cold sequence admission; later sequence changes remain rejected.

Preserve configured level/tier, close cleanly if retention fails, and cover borrowed-input overwrite and ownership. Reproduce stale reopen state in MP4 and both WebM startup modes, then validate GCC/Clang sanitizers, ten Windows32/64 runtime cases, C89 frontends, seventy-two strict stack objects and sixteen Android root links.
2026-10-22 11:24:26 +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
Codex e8733ab179 android: publish AV1 MediaCodec through Vulkan renderer admission
Select the AV1 native owner and backend in Makefile and Griffin builds, including AV1-only configurations. Extend shared device/import/cache gates, publish only after Android image admission, preserve Vulkan Video preference and withdraw with renderer lifecycle ownership.

Exercise all sixteen codec/thread source and renderer lifecycle combinations, including init retry and stale teardown. Validate GCC/Clang sanitizers, thirty-two Windows runtime cases, both C89 frontends and sixteen combined/AV1-only native Android root links. Vulkan continues to require threads; threadless source/registry exclusion is tested separately.
2026-10-22 11:10:53 +02:00
Codex f2512de8b5 av1: drain native output across terminal hidden packets
Let packet decoders accept hidden input while retaining one visible output, then drain only at real container EOF. Preserve presentation timestamps, HDR metadata and native format snapshots across delayed output. Keep surface backends on their existing contract.

Cover partial input, terminal BUSY and failure, discard and rewind in MP4/WebM, plus native format changes while an image is pending. Validate GCC/Clang sanitizers, Windows32/64 runtime cases, two C89 frontends, sixty strict stack builds and eight Android native root links.
2026-10-22 11:01: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
Codex a8286d93fa media: add bounded MediaCodec AV1 backend and registry
Normalize admitted sequence OBUs into validated av1C during cold startup and export native-grained NV12/P010 with reconciled color metadata. Bound active and retiring sessions, retain notifier ownership through deferred cleanup, and reject stale opens and foreign withdrawals by publication epoch.

Cover configuration normalization, admission failures, retained exports, color conflicts, pool reuse and concurrent renderer replacement. Factory publication and container terminal drain remain separate integration work.
2026-10-22 10:21:13 +02:00
Codex 19f37ff656 av1: admit hardware with sequence and container metadata
Pass borrowed sequence OBUs and known color/HDR metadata through an optional factory admission callback. Preserve legacy surface factories and snapshot the admitted packet mode.

Retain the initial WebM sequence bytes during cold admission so decoder reopens do not depend on the feeder keeping startup bytes available. Exercise both admission paths through MP4/WebM seek, HDR and reduced-availability cases.
2026-10-22 10:05:30 +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
Codex 1758ffd469 media: add the MediaCodec AV1 Surface decoder owner
Validate av1C sequence admission and reconcile coded/container color before native startup. Correlate hidden and repeated-display packets with Surface images using bounded local timestamps, preserve grain and crop snapshots, and defer destruction until exported leases retire.

Cover malformed configuration, input and output BUSY, PQ/HLG, grain mismatch, cropped output, delayed EOS delivery and retained leases. Include the owner in native Android signature and root-link checks; factory registration remains separate.
2026-10-22 09:50:41 +02:00
Codex 4f7abb7734 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 09:41:54 +02:00
Codex dcfdc099d6 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 09:32:28 +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
Codex 939c5ed088 media: publish admitted Android decoders from Vulkan
Publish HEVC and VP9 MediaCodec registries only for frontend devices with Android image import enabled, preserving working Vulkan Video codecs. Withdraw offers before renderer teardown and link shared imports independently of MP4 so VP9-only builds work. Keep the worker JNIEnv declaration available without legacy JNI macros and check Makefile/Griffin feature gates.
2026-10-22 08:53:09 +02:00
Codex bdb9f5a3ee media: drain terminal VP9 packets before ending WebM playback
Preserve the last visible output across trailing hidden packets and signal terminal input only after confirmed demux EOF. Poll EOS and delayed Surface delivery without waiting, retain exported leases, and keep the existing timestamp-table shortcut. Leave surface backends on their existing output path.
2026-10-22 08:34:54 +02:00
Codex d0bd247a9a media: bound MediaCodec VP9 backend retirement and registry
Own exporter pools and deferred decoder cleanup through bounded session admission. Preserve color-aware packet decoding, keep retained frame leases alive across close, and reject opens whose registry generation changes during native startup. Add conditional VP9 offer withdrawal so stale owners cannot remove a replacement backend.
2026-10-22 08:22:04 +02:00
Codex c11aae0554 media: pass WebM color metadata into VP9 admission
Allow color-aware factories to inspect container metadata before native startup. Keep legacy factories and their close snapshots, and clean up refused or incomplete opens without disabling software playback.
2026-10-22 08:14:44 +02:00
Codex 27c38530e2 media: preserve VP9 container color through MediaCodec export
Intersect VP9 color-space/range syntax with container metadata without inferring a transfer curve. Supply explicit Android input color hints and select the ten-bit HDR profile for PQ/HLG. Carry the immutable metadata to the output adapter and reuse the shared frame exporter with native-color conflict refusal. Cover input hints, PQ/HLG leases, mastering/light refinement, depth mismatches and retirement while retaining HEVC behavior.
2026-10-22 08:04:56 +02:00
Codex bc1cdfa00d media: own MediaCodec VP9 Surface output and retirement
Add a bounded VP9 decoder owner using the existing MediaCodec session and Android image queue. Match visible and repeated output by local timestamps, discard late hidden or dropped images, and retire codec resources only after leased images return. Cover input backpressure, EOS, startup faults and deferred release. Include the owner in native Android signature/link checks and update the optional-extension fixture for Linux PRIME and XR.
2026-10-22 07:44:24 +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
Codex 63f30c9569 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 07:25:52 +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
Codex a153d185ff test: compare real VA imports through Vulkan conversion
Extend the explicit native modifier gate through production submission, fence retirement and RGBA readback against an independent SDR reference. Restore claimed exports after GPU retirement to retain the parser-close lifetime checks. Exercise the new readback oracle with synthetic Vulkan planes in host CI and reject corrupted reference pixels; native VA device acceptance remains separate.
2026-10-22 07:17:04 +02:00
Codex 6e1b2318c8 test: import real VA exports on the matching Vulkan device
Add an explicit Linux modifier allocation gate to the native decode oracle. Require reported render-node identity, import both planes through production helpers, and retire each import before returning its VA claim, including retained outputs after parser close. Keep GPU sampling and pixel acceptance separate from allocation success.
2026-10-22 07:17:04 +02:00
Codex 4874c400c7 test: cover real weighted WPP HEVC streams in VA acceptance
Add four reproducible Main8/Main10 full/limited weighted-B streams with multi-row WPP and independent FFmpeg CRCs. Preserve the reference range explicitly and exercise every picture through software decode and NORMAL subset submission.

Extend the required-device oracle to 192 pictures and verify WPP entries in NORMAL mode. Native hardware success remains unexecuted without a DRM render node.
2026-10-22 07:17:04 +02:00
Codex b9198c8886 test: add required-device VA HEVC decode and export oracle
Build a Linux native oracle against the production decoder owner. Require hardware admission, compare display-order NV12/P010 output to the existing FFmpeg fixture CRCs, retain an exported surface after source close, and check FD retirement across reopened sequences.

Keep CPU image transfer and bounded polling inside the test executable. Independently validate cropped/padded image checksums and reject unavailable devices or assertions-disabled acceptance builds. The hardware success path remains unexecuted on this host without a DRM render node.
2026-10-22 07:17:04 +02:00
Codex 9d12250174 vaapi: connect NORMAL HEVC decoding to the native owner
Select NORMAL when advertised or when the API default applies, retaining BASE-only support. Preallocate both packet workspaces on the cold owner and release the parser borrow at decoder detach before deferred native retirement.

Exercise both pool modes, parsed owner decode and held exports after parser destruction, four allocation failures and partial-context termination retry. Share parser objects across the host fixtures, with separate TSan instrumentation.
2026-10-22 07:17:03 +02:00
Codex bfff7f90df vaapi: pack NORMAL HEVC slices and own WPP subset buffers
Pack escaped NALs and coded entry offsets into a preallocated owner workspace after validating every header and extent. Extend native submission to retain a fifth subset buffer through failure, completion and retirement.

Exercise Main8/Main10 I/P/B WPP packets, parsed sequence submission and all five buffer failures under sanitizers and strict C89 builds. Shipping pool/owner selection remains separate work.
2026-10-22 07:17:03 +02:00
Codex 42b32da45e vaapi: map parsed HEVC long-slice parameters
Map coded short- and long-term reference order, list modifications and effective prediction weights into VA long-slice metadata. Preserve output on refusal and account for escaped header bytes without allocation or native operations.

Exercise independent boundary/refusal cases and 64 parsed Main8/Main10 I/P/B pictures. NORMAL packet packing and native owner integration remain separate work.
2026-10-22 07:17:03 +02:00
Codex 87224b14b4 rh265: expose parsed long-slice metadata
Publish slice flags, reference-list modifications, effective prediction weights and WPP entry lengths without pixel or DPB advancement. Keep output storage separate from existing access-unit state and exercise the query through the byte-exact decode oracle.
2026-10-22 07:17:03 +02:00
Codex 90c70f94fc video: register the VA HEVC fallback factory
Keep a stable offer with atomic DRM identity and renderer epochs. Resolve the actual render node through libdrm, preserve Vulkan Video preference, and transfer closed or partially opened owners to bounded cold retirement workers. Refuse admission without threads or lock-free atomics.
2026-10-22 07:17:03 +02:00
Codex 61db8ae149 video: preserve track HDR metadata in VA imports
Accept the supported YUV flag bits, carry container HDR metadata into conversion, and allow track-visible dimensions within the decoded surface. Preserve specified coded color while using track metadata for unspecified values and light levels.
2026-10-22 07:17:03 +02:00
Codex b75a7f373c video: own cold VA session setup and deferred cleanup
Keep the runtime, native pool, packet workspace and decoder adapter under one owner. Preserve partial setup failures and renderer-held exports, poll pending work once per retirement pass, and retain libraries and context through termination retries. Registration and executor policy remain with the factory.
2026-10-22 07:17:03 +02:00
Codex fdd27c04ce video: adapt VA HEVC pool completion to opaque frame leases
Connect parsed-picture submission and bounded completion polling to opaque output carriers. Keep crop/color metadata immutable and detach without freeing renderer-held frames; the decoder owner retries native returns before retiring the adapter. The native pool, session and runtime stay explicitly borrowed.
2026-10-22 07:17:03 +02:00
Codex d4932e8f25 video: connect optional VA cache to Vulkan renderer lifetime
Detect Linux HEVC/PRIME2 headers without a link-time VA dependency. Route opaque leases through the existing queue callbacks, poll returns on renderer ticks, and retain failed source/destination use through exact-device retirement. Cover native resolution and adapter transport with portable typed fixtures.
2026-10-22 07:17:02 +02:00
Codex 3eff27b52c video: own bounded VA Vulkan cache and deferred retirement
Reuse immutable imported-image identities with the opaque lease bridge and owned conversion resources. Poll completion during paused ticks, retain uncertain submissions through exact-device teardown, and retry failed owner returns. Cover reuse, eviction, allocation failures and retirement with portable fixtures.
2026-10-22 07:17:02 +02:00
Codex 28221097e0 style: follow project conventions in recent VA migration code
Use Allman braces, three-space indentation, separate statements and readable alignment. Initialize fixed Vulkan resource, barrier and submission structs through explicit member stores while retaining clearing for project-owned state.
2026-10-22 07:17:02 +02:00
Codex de28925f7b vulkan: own cold VA conversion resources
Create bounded sampler, descriptor, pipeline, command and fence resources for persistent VA conversion entries. Unwind partial creation in dependency order and preserve exact-device teardown. Exercise the production owner in the native pixel oracle across all 32 slots.
2026-10-22 07:17:02 +02:00
Codex 60834a9628 vulkan: bridge opaque VA leases through conversion fence retirement
Borrow completed exports for cold import preparation, then claim and adopt matching opaque frames for warm conversion. Restore refused submissions and publish completed exports to decoder collection without native calls on the renderer. Retain uncertain submissions and failed returns through exact-device teardown.
2026-10-22 07:17:02 +02:00
Codex 0a69266bc2 vulkan: reuse imported VA images across returned frame leases
Capture unique DMA-BUF identities on the decoder owner. Return completed export tickets independently of cached imports, and re-adopt only matching buffers and layouts without renderer-side native calls. Require per-picture submission reconfiguration and retain pending or uncertain GPU ownership.
2026-10-22 07:17:02 +02:00
Codex 2fd1326943 tests: stub software fallback in GPU format transport fixture
Keep the format-only harness independent of the player worker while satisfying callback references retained by the MinGW linker. Count either fallback call as a test failure.
2026-10-22 07:17:02 +02:00
Codex 22de713719 video: carry VA exports through opaque owner-return leases
Publish completed exports through the existing internal hardware-frame callback contract. Bounded lock-free claim/take/restore/return states retain tickets through GPU use; only the decoder owner collects and closes returned exports. Add stale/abandonment and 10000 SPSC handoff tests with an isolated TSan target; refuse unsupported atomics without changing frame ABI.
2026-10-22 07:17:02 +02:00
Codex 0beecd021c vulkan: execute VA conversion submission pixel oracle
Exercise real exportable plane images, foreign ownership, destination layouts and fences through the production command helper under Vulkan validation. Compare cropped 8/10-bit SDR and PQ/HLG RGBA8/FP16 pixels with independent references. This proves conversion commands, not native VA DMA-BUF decode/import.
2026-10-22 07:17:02 +02:00
Codex 3ad02e2935 vulkan: record warm VA plane conversion submissions
Use borrowed sampled-YUV pipeline resources and immutable crop/color uniforms. Preserve source foreign ownership, tracked destination layout and existing queue serialization. Busy or stale entries submit nothing; uncertain GPU submission retains the source through device retirement.
2026-10-22 07:17:01 +02:00
Codex 3076c7e936 vulkan: retain VA export tickets through imported frame use
Move a VA export only after both sampled layer imports succeed. Record paired foreign-queue barriers and retain resources across pending or failed renderer fences. Retire Vulkan resources before returning the owned export through the decoder-owner handoff; device-destruction recovery makes no invalid Vulkan calls.
2026-10-22 07:17:01 +02:00
Codex b97e28a05b vulkan: own DRM modifier image imports and memory
Create explicit sampled images, validate FD size and image/FD memory-type intersections, then import CLOEXEC duplicates and bind memory planes. Honor dedicated requirements and unwind view, image, memory and unconsumed FDs on refusal. This remains private resource-cache groundwork; producer completion, foreign ownership and VA ticket retirement are separate admission gates.
2026-10-22 07:17:01 +02:00
Codex 9eb993f011 vulkan: preflight exported VA layer formats and modifiers
Query sampled DRM modifier planes and external DMA-BUF image limits before taking FDs. Admit both NV12/P010 layers atomically and reject dedicated-only disjoint imports. This prepares the private VA importer; no renderer capability or decoder registration is enabled.
2026-10-22 07:17:01 +02:00
Codex d247c3b353 vulkan: negotiate optional PRIME device prerequisites
Enable the complete DMA-BUF/modifier/foreign-queue bundle only on frontend-owned flat Linux devices with a render identity and compatible API dependencies. Keep core and XR device requests unchanged, preserve the actual enabled state across cache reuse, and do not advertise surface import support. On creation refusal, retry without PRIME using a copied enable list while retaining video queues/features. Validate version/missing-extension gates, candidate fallback, local Vulkan playback/validation, native ABI checks and Android root links.
2026-10-22 07:17:01 +02:00
Codex be479b7b6e vulkan: cache the selected GPU render-node identity
Query advertised DRM properties during device initialization and expose bounded render-node major/minor metadata through the internal device handoff. Retain the actual instance API/properties2 capability through cached-instance reuse and refuse queries for an unverified instance. Device reads copy cached metadata without queries on playback paths. Validate fault gates, native context/cache lifecycle, builtin media Vulkan playback, legacy syntax, Android roots and production stack budgets. This does not advertise PRIME import support or activate VA decoding.
2026-10-22 07:17:01 +02:00
Codex 727936f331 video: own VA PRIME exports through surface retirement
Retain completed pool tickets while importing or using exported PRIME objects. Validate bounded NV12/P010 descriptors, preserve modifiers and auxiliary planes, seal FDs close-on-exec and transfer each FD only once. Close partial exports without duplicate FD closes, including descriptor zero. Bind the real export API and test metadata/fault ownership on Linux and Windows; hardware export and renderer import remain unvalidated.
2026-10-22 07:17:01 +02:00
Codex 631d770472 video: retain VA surfaces across reads and exported frames
Track bounded in-flight submissions separately from shared DPB selection. Prevent surface reuse while a write, later GPU reader or exported incarnation retains it; validate reference picture identity and decode serials. Preserve ticket generations across cold reset, allow exactly one frame ticket per incarnation and retain broken owners until display termination. Exercise real parsed batches, delayed/out-of-order completion, stale returns, cleanup retries and failure recovery on Linux and Windows.
2026-10-22 07:17:01 +02:00
Codex 54909c62d6 video: pack parsed HEVC pictures for VA short submission
Build BASE-mode start-code slice buffers with escaped payloads and aligned zero padding using preallocated storage. Connect native picture/matrix translation and bounded submission without advancing the shared DPB until acceptance. Refuse unsupported modes, malformed metadata, overlapping storage and capacity failures before writing output. Roundtrip 64 real Main/Main10 I/P/B packets and exercise failure boundaries on Linux and Windows; native VA pixels remain untested.
2026-10-22 07:17:00 +02:00
Codex 89a310a69a video: retain VA decode submissions through completion
Keep bounded picture, matrix, slice and data buffers until zero-timeout surface synchronization completes. Refuse reuse while pending, retain failed destruction for retry and poison uncertain native failures until display termination. Bind the six native submission entrypoints and preserve explicit short/long slice mode. Exercise both modes and depths, partial creation, duplicate IDs, native failure, polling and retirement on Linux and Windows.
2026-10-22 07:17:00 +02:00
Codex b0c79b4b01 video: own the VA-API DRM runtime lifecycle
Bind the actual VA/DRM ABI, validate the renderer node identity, and retain display, libraries and FD until termination succeeds. Once termination starts, retry only vaTerminate because a failed call may already unload its driver. Exercise partial initialization and teardown on Linux and Windows; verify real library binding and rejected Linux devices without claiming hardware decode.
2026-10-22 07:17:00 +02:00
Codex 389516d408 video: own VA-API HEVC context admission and retirement
Validate the requested profile, decode entrypoint, slice mode and surface format before creating a bounded context. Preserve partial native ownership across setup errors and retry failed destruction in dependency order. A closing owner can release remaining IDs only after successful display termination. Verify the real SDK function signatures and exercise allocation, creation and retirement faults without registering a decoder backend.
2026-10-22 07:17:00 +02:00
Codex b9d5dd0460 video: map prepared HEVC pictures to VA-API parameter buffers
Translate shared HEVC picture/reference metadata and effective raster scaling lists to the native libva ABI without allocating or taking a frame lease. Track slice types across the access unit so mixed-slice pictures set NoBiPredFlag correctly. Cover parsed Main/Main10 I/P/B streams and invalid reference/surface descriptions. This is buffer preparation only; no VA backend is registered.
2026-10-22 07:17:00 +02:00
Codex 29f05b9f4a mp4: keep software decoding after hardware admission fails
Clear the hardware preference when H.264 or HEVC admission fails. A later rewind must not silently enable hardware after the player selected its software sink; an explicit admission request can retry. Cover refusal followed by rewinds and an explicit retry.
2026-10-22 07:17:00 +02:00
Codex 026ec76bde player: schedule cold hardware admission on the decoder worker
Queue initial hardware admission before starting the sink, reusing its job block for normal decoding. Preserve polling-thread sink callbacks and stream ownership through queued or running cancellation. Keep inline frame decoding and the no-thread path, and only query existing hardware when the worker is unavailable.
2026-10-22 07:17:00 +02:00
Codex 9b12d82ef5 media: complete HEVC drain without losing delayed Surface frames
Invoke the backend drain hook even when no parser display slots remain. Report confirmed missing codec output at EOS instead of waiting indefinitely, while retaining BUSY for a rendered Surface image that has not arrived yet. Preserve pressure-only output handling and make drain failures sticky.
2026-10-22 07:17:00 +02:00
Codex 5ccfb36f1d media: guard Android decoder admission across driver replacement
Keep registry offers and decoder configuration stable while a generation gate controls new sessions. Reject and asynchronously close opens that overlap withdrawal or replacement. Existing sessions retain their callbacks; stale owners cannot withdraw a replacement. Stop admission permanently before generation reuse.
2026-10-22 07:17:00 +02:00
Codex a43188f642 media: retire Android decoders without caller locks or joins
Publish retirement through an eventcount to a detached worker. Keep publisher and worker references until notification and cleanup both finish, including failed startup. Exercise delayed workers and notifier lifetime under sanitizers and provide a no-native-fence host mode for the Windows lifecycle fixture.
2026-10-22 07:16:59 +02:00
Codex 8bff865216 media: preserve container HDR metadata in Android imports
Resolve container mastering and light levels against the native frame before cache lookup and submission. Inherit unspecified fields and reject explicit CICP or range conflicts without consuming the lease.
2026-10-22 07:16:59 +02:00
Codex 344648123e player: recover in software when the first hardware frame is refused
Quiesce queued decode work and restart on its exclusive owner before initializing the software sink. Preserve the movie clock and prevent reselection of the failing backend. Keep inline queued frame ownership until sink release. Hardware-only codecs still require an available software decoder to recover.

Validate startup, decode and import refusal; bounded restart, failed/canceled recovery, held slots and old exported leases. GCC/Clang sanitizers, TSan, native MinGW thread/no-thread tests, C89 application link, MSVC6 stand-in, stack checks and expanded NDK root/player links pass. Environment-dependent compile-matrix skips remain unverified.
2026-10-22 07:16:59 +02:00
Codex e411036f18 media: add bounded Android HEVC backend lifecycle and native link checks
Compose the Surface decoder and opaque frame exporter with pre-created cold retirement workers. Bound admission across live and retiring sessions, preserve old leases across seek epochs, and roll back JNI/native/thread startup failures. Registration remains disabled pending native playback acceptance.

Scope C89 static-inline compatibility to the Android native-window SDK header. NDK r29 otherwise emits duplicate ANativeWindow_clearFrameRate definitions and an unused newer-API dependency under GNU89 inline semantics. Validate helper and shipping renderer/context objects together with relocatable links.

Validation: GCC/Clang C89 ASan+UBSan and TSan lifecycle tests; held real exporter lease; native/thread/detach failure injection; 136 NDK individual compiles, eight unity builds, sixteen shipping root compiles and eight relocatable links. Actual Android playback is not executed.
2026-10-22 07:16:59 +02:00
Codex 010683ca31 vulkan: integrate Android frame imports with renderer cache and staged teardown
Recognize opaque Android leases before raw VkImage interpretation, validate source geometry and color, prepare immutable per-buffer conversion resources on first use, and route submissions through the existing queue lock. Preserve the previous destination on busy or failed conversion.

Destroy deferred Vulkan children before vkDestroyDevice as required by VUID-vkDestroyDevice-device-05137, while retaining external AImage leases until device destruction returns. Transfer dedicated command-pool and deferred destination ownership with the cache. Decoder registration remains disabled pending native Android validation.

Validation: GCC/Clang C89 ASan/UBSan component and shipping-helper tests; eight real NDK renderer/context/unity configurations; GCC/Clang non-Android shipping renderer/context compilation. Native Android playback is not yet executed.
2026-10-22 07:16:59 +02:00
Codex 0dfc3b8124 vulkan: retire deferred Android image caches after actual device destruction 2026-10-22 07:16:59 +02:00
Codex b1bf9ee6b1 samples: verify Android cache holds failed submissions until device destruction 2026-10-22 07:16:59 +02:00
Codex 8cd312ba2f vulkan: route Android imports through renderer queue submission callback 2026-10-22 07:16:58 +02:00
Codex 3ae1b5ad3b vulkan: negotiate Android import features only on frontend-owned devices 2026-10-22 07:16:58 +02:00
Codex 746b5d3db2 media: prefix Android helpers and validate native unity compilation 2026-10-22 07:16:58 +02:00
Codex dbcd658b9c media: retain Android decoder owner until import context retires 2026-10-22 07:16:58 +02:00
Codex 94b995efc7 video: bound Android Vulkan import cache and evict only retired entries 2026-10-22 07:16:58 +02:00
Codex 720fbf5056 video: create and retire cold Android Vulkan import entries 2026-10-22 07:16:58 +02:00
Codex e0ca7b3b0d video: submit warm Android image conversions through GPU fence retirement 2026-10-22 07:16:58 +02:00
Codex 202ec8e8f6 video: scope Android image ownership barriers to compute or fragment reads 2026-10-22 07:16:58 +02:00
Codex 26069ee954 media: export HEVC Android image leases with verified color metadata 2026-10-22 07:16:58 +02:00
Codex 2bc182435e video: retain Android image tickets through opaque renderer frame leases 2026-10-22 07:16:57 +02:00
Codex a83d3c997a test: execute external RGB shader crop and HDR pixel oracle 2026-10-22 07:16:57 +02:00
Codex ac2eb32e7a video: build immutable external-sampler compute pipeline resources 2026-10-22 07:16:57 +02:00
Codex a0d7749922 video: convert external sampler RGB through the shared HDR shader 2026-10-22 07:16:57 +02:00
Codex fa43677c9a video: record Android foreign image acquire and release barriers 2026-10-22 07:16:57 +02:00
Codex 83579a04d8 media: validate native Android buffer allocation before GPU import 2026-10-22 07:16:57 +02:00
Codex ff1ceafe1d video: transfer Android readiness and completion through Vulkan sync fds 2026-10-22 07:16:57 +02:00
Codex b77d37a65d video: import Android Vulkan external image resources with dedicated memory 2026-10-22 07:16:57 +02:00
Codex 954539d87e media: return Android pictures excluded from display immediately 2026-10-22 07:16:57 +02:00
Codex 275eafab01 test: cover Android HEVC startup refusal and malformed timestamps 2026-10-22 07:16:57 +02:00
Codex b3789ce352 test: keep the Android HEVC fixture whitespace clean 2026-10-22 07:16:57 +02:00
Codex f8444c1d98 media: own HEVC Surface decoding through Android image retirement 2026-10-22 07:16:57 +02:00
Codex 256a5f48f9 media: expose image retirement before stopping the Android codec 2026-10-22 07:16:56 +02:00
Codex 82be334b42 h265: let asynchronous hardware backends submit explicit EOF 2026-10-22 07:16:56 +02:00
Codex e81d767dae media: validate Android native ABI with the pinned NDK
Scope the modern native_window.h inline spelling to its SDK includes for strict C89. Compare every loaded media function pointer with its SDK declaration without introducing direct symbol references. Cover both Android file-offset ABIs in cross-compile CI.

All 40 actual NDK r29 combinations pass locally: ARM32/64, API 21/29, five sources and default/64-bit offsets. Undefined-symbol inspection confirms no newer media APIs became mandatory load dependencies. Decoder/GPU/device acceptance remains pending.
2026-10-22 07:16:56 +02:00
Codex 6aa0e45313 test: roundtrip real HEVC parser output through Android packet packing
Generate Main/Main10 test samples with test-only libx265, feed packed parameter sets through rh265, verify retained bytes and access-unit metadata, and roundtrip every escaped VCL NAL through Annex-B packing. Exercise the shipping parser under GCC/Clang C89 ASan/UBSan.
2026-10-22 07:16:56 +02:00
Codex 11e76e7abf media: pack validated HEVC NALs for Android Surface decoding
Construct Annex-B VPS/SPS/PPS csd-0 and complete VCL access units from existing rh265 parser views. Preserve escaped bytes, validate type/temporal consistency and exact coded sizes, bound staging needs, and reject overlapping input before modifying output. No allocation or decoder capability activation.

GCC and Clang C89 ASan/UBSan tests cover exact lengths, emulation-prevention bytes, insufficient storage, mismatched units and alias refusal. Actual native MediaCodec decode and GPU import remain pending.
2026-10-22 07:16:56 +02:00
Codex 4f39c8a27a media: add runtime MediaCodec Surface decoder session
Create only an explicitly admitted hardware component and verify its actual name. Configure bounded codec-specific CSD and whole access-unit input, use zero-timeout input/output dequeue, track output format/crop/color without invented metadata, and deliver Surface buffers with explicit presentation timestamps. Teardown belongs to the decoder worker after GPU image retirement.

GCC/Clang C89 ASan/UBSan stand-in tests cover missing symbols, partial creation, identity mismatch, input backpressure/overflow, format changes, EOS and sticky failures. Actual NDK compilation, native decoding and renderer integration remain pending.
2026-10-22 07:16:56 +02:00
Codex 42f0cc2075 media: retain Android reader images through GPU fence retirement
Add bounded cold image ownership and runtime-loaded AImageReader PRIVATE/GPU-sampled output. Keep native images, reader and media library alive through renderer leases; perform only zero-timeout fence polls and native image destruction on the worker. Renderer returns transfer a completion fence without allocation or native image calls.

GCC/Clang C89 ASan/UBSan tests cover delayed ready/done fences, stale tickets, malformed metadata, missing symbols and partial startup. A 10000-handoff worker/render test passes ThreadSanitizer. Actual NDK compilation and Android decoder/GPU integration remain pending.
2026-10-22 07:16:56 +02:00
Codex 5c2db2becf media: add cold Android hardware codec inventory admission
Query explicit API 29 hardware identity, direction, aliases and profile/size support through bounded JNI local frames. Reject unknown/software/contradictory identities and preserve caller exceptions. No decoder or renderer capability is enabled.

Exercise real JNI with synthetic Android codec classes, vendor/metadata faults and 10000 local-frame iterations under GCC/Clang C89 and UBSan. Add actual NDK 32/64-bit API 21/29 cross-compile gates; native device/session/import acceptance remains pending.
2026-10-22 07:16:56 +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
M00NR00STERandLibretroAdmin 4592b490bd Android: Samsung S-Pen stylus support (#19076)
* android: Add Samsung S-Pen stylus input support

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

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

Closes #15490
Closes #18194

* overlay: hide for a stylus without unloading the pack

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-02 05:48:02 +02:00
libretroadmin 11dc8ba30d apple: report the refresh rate of the screen the window is on
The Apple display server answers get_window_refresh_rate from the screen
the RetroArch window is on: on macOS the current mode of that display,
or, on a built-in panel whose mode carries no rate, the screen's own
maximumFramesPerSecond; on iOS and tvOS that screen's
maximumFramesPerSecond, which follows an iPad onto an external display
and a TV through a mode change. With no window yet the frontend falls
back to get_refresh_rate as before.

The reading is taken again whenever the window changes screen or a
screen changes mode: macOS posts NSWindowDidChangeScreenNotification
and NSApplicationDidChangeScreenParametersNotification, iOS and tvOS
the UIScreen mode, connect and disconnect notifications, and an
observer the display server installs at init passes each on as
video_driver_window_output_changed().
2026-10-02 05:42:40 +02:00
LibretroAdmin ae24f6b50d android: let the system keyboard's Backspace reach its text field
With "Use System Keyboard" on, the text being typed lives in a
Java-side EditText and the menu line is a copy of it: every change to
the field sends the whole text across. A soft keyboard commits most
text straight into the field, but sends some keys as key events
instead - Backspace when nothing is being composed (so always in
something like an IP address), digits on the AOSP keyboard.

RetroActivity is a NativeActivity, so those key events come out of the
native input queue first, and the views only see one that native code
finishes unhandled. android_input_poll_input_default finished every
key event handled, so a Backspace never reached the field. Its text
kept the characters, and the next character typed sent them back:
"192.168.17888", Backspace twice, "." gave "192.168.17888.".

While a system keyboard session is open, a key event the soft keyboard
sent - AKEY_EVENT_FLAG_SOFT_KEYBOARD or the virtual keyboard's device
id - is now finished unhandled and kept out of the native key state
and line editor, so the framework forwards it to the field. BACK stays
native: the field takes it ahead of the IME to cancel, and one left
unhandled would fall through to the activity's own back handling.
Hardware keyboards and gamepads are untouched.

The decision sits in input/drivers/android_kbd_route.h, free of NDK
types, so samples/input/android_kbd_keys can build it on a host. That
harness runs the reported sequence through the queue-then-views
pipeline and asserts the old behaviour is caught; it is wired into
the samples/input workflow.
2026-10-02 05:30:33 +02:00
libretroadmin 74935dc166 menu: Menu Frame Rate replaces Throttle Menu Framerate
Menu Frame Rate sets the rate the menu runs at while content is loaded,
in every sync mode: 'Display Rate', the default, runs it at the refresh
rate of the monitor the window is on, and 'Content Rate' at the
content's frame rate. At 'Display Rate' the silence the menu feeds is
written without blocking and measured out by the clock, so audio no
longer holds the menu to the content's rate; a core left running behind
the menu keeps its own clock, running when its next frame is due while
the menu re-presents its last frame in between; and behind the threaded
video wrapper without the display pacing hold, the timer holds the menu
to the display's period. A configuration with Sync to Exact Content
Framerate and Throttle Menu Framerate on keeps the content rate it had,
and the old key is dropped on save.

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

The generated intl tables still name the toggle's VALUE and SUBLABEL
ids, so those two stay in msg_hash.h as retired rows until the next
Crowdin fetch regenerates the tables without them; a follow-up
deletes the two rows then. help_list stays: its "Help" string is the
title of the per-setting help popup.
2026-10-02 05:19:13 +02:00
LibretroAdmin f705c273a1 menu: stop loading the unused Help icon textures
With the Help list wiring gone, the only remaining use of the Help
icon in XMB, Ozone and MaterialUI was loading it: every menu init
still read and uploaded menu_help.png / help.png for an entry that
is never drawn. Drop the texture slots and their file names.
2026-10-02 05:05:25 +02:00
LibretroAdmin 6cc0db0476 menu: remove the dead Help list wiring
The main menu stopped adding the Help entry long ago, and nothing
builds items for DISPLAYLIST_HELP_SCREEN_LIST, yet the plumbing for
it stayed: the display list type, its deferred push, the ok/title/
sublabel table rows, and the XMB/Ozone/MaterialUI icon cases. The
"Show Help" toggle under Menu > Views was also still listed even
though nothing reads menu_show_help, so it did nothing.

Remove the wiring and take the toggle out of the Views list. The
help_list and menu_show_help setting rows and their strings stay for
now: the generated intl tables reference their message ids, so
dropping them has to follow a Crowdin sync. DISPLAYLIST_HELP (the
per-setting help popup) is unrelated and untouched.
2026-10-02 05:04:05 +02:00
Vegard Bieker Matthey 89f63106e8 menu_displaylist: display releasemonth as a month name
In Information > Database Entry, Release Date Month showed the raw
number ("1"). Show the month name ("January") instead, through twelve
new translatable MENU_ENUM_LABEL_VALUE_RDB_ENTRY_RELEASE_MONTH_*
strings; languages without a translation yet fall back to English as
usual.

A month outside 1-12 in the database keeps the old behaviour and
shows the number, rather than an unresolved label.

Reworked from #19114: squashed, trailing-whitespace churn dropped so
it applies to current master, invalid-month fallback added, helper
returns enum msg_hash_enums from a static const table.
2026-10-02 04:38:34 +02:00
LibretroAdmin 925a185fe5 webOS: start Load Content on /media/internal
With no start directory set, Load Content on webOS opens on nothing
useful. Default the menu content directory to /media/internal, the
TV's shared storage, when it exists. Only a fresh config picks this
up; an existing retroarch.cfg keeps its saved rgui_browser_directory,
and nothing else about the config location changes.
2026-10-02 04:19:53 +02:00
LibretroAdmin 496c56dc92 menu: stop Parent Directory short of a root the drive list omits
The previous commit stopped offering "/" in the webOS drive list, but
Parent Directory could still walk up into it. Rather than special-case
webOS in the file browser, let the frontend say whether its drive list
offers the root: a new optional root_in_drive_list hook on
frontend_ctx_driver_t (NULL means it does), wired for the unix
frontend to frontend_unix_root_in_drive_list().

filebrowser_parse() then hides Parent Directory when going up would
land on the root and the root is not offered. Back returns to the
drive list as before. Rooted users can still open "/" deliberately
when the platform offers it; on webOS and Play Store Android, where
it is not offered, the browser no longer wanders into it.

Every other frontend leaves the hook NULL and is unchanged.
2026-10-02 04:19:50 +02:00
LibretroAdmin 0a531cdf60 webOS: do not offer the filesystem root in the drive list
Under jailer the root is not user storage, and opening it from Load
Content has been reported to freeze the file browser (PR #19203).
The webOS drive list already lists the real storage locations
(/media/internal, /tmp/usb and, in developer mode,
/media/developer/temp); stop appending "/" after them.

The root decision moves into frontend_unix_root_in_drive_list(),
which also carries the existing Play Store Android rule, so the next
commit can expose it to the file browser.
2026-10-02 04:19:47 +02:00
Acts1631 b396f73399 menu: bound playlist sublabel core name before appending
Playlist core names are copied from .lpl entries without a length
limit. strlcpy returns the source length on truncation, so using it
as the next offset can place the runtime text write beyond the
sublabel buffer. A crafted playlist with runtime data can corrupt
memory when its entry is displayed.

Stop appending when the prefix or core name exhausts the buffer.
2026-10-02 02:35:09 +02:00
libretroadmin 791657a40a CI: run the autoconfig suite under ASan + UBSan and TSan as well
The suite drives the whole frontend - runloop, task queue, autodetect - and fails on any sanitizer report, so it gains a sanitized lane for each, and a ThreadSanitizer warning now fails it as an error does.
2026-10-02 02:11:04 +02:00
libretroadmin 22571cbe42 task_queue: hand the fast-cores preference to the worker atomically
The preference is stored with release ordering on the main thread and loaded with acquire ordering by the worker as it starts, so a worker started while the setting is applied reads it without a data race.
2026-10-02 02:11:00 +02:00
libretroadmin 4bac9a1a01 runloop: count no core runtime without a frame rate
A core that reports no frame rate, such as the dummy core, adds nothing to the core runtime counter, and neither does one whose rate is so small that a frame would outlast a day; the frame time is no longer derived from a division by zero and a conversion of infinity. Every other rate is counted exactly as before.
2026-10-02 02:10:56 +02:00
LibretroAdmin 4c8630ba7f rmodtracker: slide IT panning the IT way
IT's Pxy shared the XM handler: right by x and left by y per tick, in
the engine's 0-255 units, and no fine slides. IT slides the other way
- Px0 left, P0y right - in its 0-64 units (four engine steps each),
and PxF / PFy are single fine steps on the row. A song panning with
Pxy had its channels drift the wrong way at a quarter of the speed:
its left/right balance was a median 2.8 dB off OpenMPT, now 0.1 dB.
2026-10-02 01:55:14 +02:00
LibretroAdmin 276918e82d rmodtracker: pan IT notes from their instrument, sample and envelope
Three ways IT notes landed in the wrong place in the stereo field,
compared with OpenMPT and Impulse Tracker:

- Pan envelope nodes are signed (-32 left .. +32 right, 0 centre), but
  the loader read them unbiased like volume nodes, so 0 meant hard
  left and negative nodes clipped to it. Songs with pan envelopes
  played lopsided and 2-2.7 dB loud.
- An instrument's default pan was ignored, and a sample's default pan
  overwrote the channel pan for good. IT gives each new note its own
  pan - instrument default if set, else the channel's, the sample's
  over either, then pitch-pan separation - and leaves the channel pan
  alone until a pan effect sets both.
- Pitch-pan separation was not applied.

On the 40-file corpus the worst stereo case, where instruments carry
default pans, goes from a median 4.5 dB left/right balance difference
to under 1 dB, and the three pan-envelope songs from +2.0/+1.6/+1.1 dB
to within 0.2 dB.
2026-10-02 01:55:10 +02:00
LibretroAdmin accf23a2d2 rmodtracker: play IT notes the way OpenMPT and Impulse Tracker do
Three note-level divergences from OpenMPT, found rendering 40 IT files
from modarchive against libopenmpt:

- A tone portamento on a channel with nothing sounding plays as an
  ordinary note in IT; rmodtracker kept it a slide with no source, so
  the note never sounded. An Impulse Tracker 2.15 song that opens every
  part this way was silent for its first 36 s.
- Exx, Fxx and Gxx share one memory in IT (Gxx its own with the
  compatible-Gxx header flag); rmodtracker kept three, XM-style. The
  volume column's Gx now uses IT's speed table and that memory, and its
  Ex/Fx pitch slides, which the loader dropped, are played.
- The instrument keyboard's note byte was ignored, so instruments that
  map keys to other notes (drum kits, transposed instruments) sounded
  at the key pressed - several semitones off in one song. Notes now
  also reach IT's C-0, eleven semitones below the engine's lowest key.

On the 40 files the share of 50 ms windows more than 6 dB off goes
from 67.6% to 2.5% for the portamento song (spectral match 0.66 ->
0.93), and the two keyboard-mapping songs' spectral match from 0.47
to 0.77 and 0.59 to 0.91.
2026-10-02 01:55:05 +02:00
LibretroAdmin 67356404af rmodtracker: match OpenMPT's IT mix levels and pattern length
Files ModPlug Tracker wrote (or OpenMPT in its ModPlug mode) are played
by OpenMPT at ModPlug's mix level, which pre-attenuates by a table of
the channel count; rmodtracker played them at Impulse Tracker's level,
3.4 dB (4-5 channels) to 9.5 dB (26 channels) louder, so they clipped.
Detect them with OpenMPT's header rules (cwt/cmwt pairs such as
0x217/0x200 and 0x888, or an OpenMPT version with "OMPT" in the
reserved field), read the mix-level mode ("PMM.") and sample pre-amp
("SPA.") from OpenMPT's song extensions when present, and scale the
gain as OpenMPT does: x64 / table[channels / 2] for ModPlug's levels,
x2 for 1.17RC3, the 1.17RC1/RC2 and FT2 variants likewise. The gain is
capped at 255 to keep the volume products within 32 bits.

Also accept patterns up to 1024 rows: Impulse Tracker stops at 200 but
OpenMPT writes more, and a longer pattern was replaced by 64 empty
rows (one OpenMPT song played 3 s of 98).

Across 40 IT files from modarchive rendered against libopenmpt 0.7.3,
the eight ModPlug/OpenMPT-levelled ones go from +3.4..+9.5 dB (one
OpenMPT 1.17RC3 file -6 dB) to within 1 dB.
2026-10-02 01:55:01 +02:00
libretroadmin 83895aef5b autodetect: order each port's connects and disconnects by sequence
Every connect and disconnect queued for a port takes the next number in
that port's sequence, and only the operation holding the newest number
writes the port's state when it completes, so a multi-pass connect scan
can no longer overwrite a disconnect or a different device queued after
it. A connect is skipped only when the same device's connect is already
the newest thing pending on the port, and a disconnect only when a
disconnect is; a device that is swapped or replugged while an earlier
scan is still running is configured rather than dropped.

The autoconf suite gains a phase that runs the connect/disconnect cycle
in several instances at once, so every step is queued before the first
scan as it is on a busy CI runner, and checks that the device connected
last is the one configured.
2026-10-02 01:36:23 +02:00
LibretroAdmin b990b4cfae win32: use retro_atomic for the deferred menu-command mailbox
The mailbox added for #19665 spelled its slots as volatile LONG with
InterlockedCompareExchange/InterlockedExchange. Use retro_atomic_int_t
with retro_atomic_cas_int/retro_atomic_exchange_int instead, like the
rest of the tree: same lock-free behaviour on Windows, and the slots
are real atomics to the compiler and to TSan rather than volatile.
2026-10-02 01:22:47 +02:00
LibretroAdmin 045c2d52b5 win32: run menu-bar commands on the main thread (#19665)
With threaded video the RetroArch window is created on the video
thread, so the menu bar's WM_COMMAND is handled there. Window Scale
calls CMD_EVENT_REINIT, whose driver teardown sends CMD_FREE to the
video thread and joins it - from the video thread itself. It waits
for itself forever, the window stops pumping, and the process has to
be killed from Task Manager.

win32_menu_loop() now parks the command when it is not on the main
thread, in a small lock-free mailbox, and the main thread's pump
(ui_application_win32_process_events, which the run loop calls every
iteration) runs it. That is the same thread the in-menu Windowed
Scale setting already runs on. The video thread also pumps through
that function, so draining is gated on task_is_on_main_thread().
Non-threaded video is unchanged: the window is on the main thread and
commands run immediately as before.

This covers every menu-bar entry, not just Window Scale: the others
(fullscreen toggle, save/load state, disc control, core/content load)
were also running run-loop work on the video thread.
2026-10-02 01:20:55 +02:00
LibretroAdmin 1b8917df23 rmodtracker: honour IT's linear slides flag
The IT loader never set linear_periods, so every IT file slid with
Amiga periods, where a slide's pitch change depends on the note. With
header flag 3 set (the default in Impulse Tracker, and set in all four
test files) Exx, Fxx and Gxx move xx * 4 sixty-fourths of a semitone a
tick on any note, which is exactly the engine's XM linear-period
arithmetic. Select it from the flag.

With the two pitch fixes before it, the pitch offset to it2play goes
from about 3 cents to 0, and over the three patches the share of 50 ms
windows more than 6 dB off it2play goes 0.3% -> 0.0% for Deus Ex - The
Illuminati, 1.0% -> 0.1% for Landing (mostly the pitch envelope fix)
and 0.8% -> 0.9% for through_the_infinite (OpenMPT: 0.5% -> 0.4%).
2026-10-02 00:52:16 +02:00
LibretroAdmin 2a3cb1edad rmodtracker: keep linear-period pitch on the reference rate
The linear-period frequency multiplied by c2_rate >> 4, dropping the
rate's low four bits (8363 Hz became 8352), and the IT loader
truncated each sample's C-5 speed to 1/128 of a semitone; together
every linear-slide note played about 3 cents flat of Impulse Tracker,
OpenMPT and FastTracker 2's 8363 * 2^((4608 - period) / 768).

Add the dropped low bits back as a second 32-bit product, and round
the IT tuning to the nearest 1/64 semitone, the step the period
arithmetic keeps. Across a spread of C-5 speeds and octaves the error
goes from -2.3..-4.0 cents to -1.3..+0.7. This also corrects XM, whose
linear periods use the same formula.
2026-10-02 00:52:13 +02:00
LibretroAdmin 709391c03b rmodtracker: apply IT pitch envelopes as an offset, not a slide
Impulse Tracker resets each voice's frequency every tick and applies
the pitch envelope on top: an envelope value v puts the note v/2
semitones away from its pitch (v * 32 / 768 octaves). rmodtracker
instead multiplied the period by it every tick, so any non-zero value
kept sliding the note further away for as long as it held - a flat
+4 envelope swept the note up by two semitones a tick rather than
holding it two semitones up.

Keep the offset per channel and apply it in the frequency calculation,
for linear and Amiga periods. Landing.it, whose instruments 6 and 17
carry pitch envelopes, goes from 1.0% to 0.1% of 50 ms windows more
than 6 dB off it2play.
2026-10-02 00:52:09 +02:00
LibretroAdmin c554c21e5f samples/menu/dirwalk: pair bench rounds with blocking walks
The bench gate timed the blocking reference once, before the task
queue existed, and the deferred walk much later; drift between those
two moments on a shared CI runner failed the gate on unchanged code
(24.0ms vs 17.1ms, ratio 1.40 against a 1.35 limit).

Time a blocking walk immediately before each frozen deferred round
and gate on the best per-pair ratio over five pairs: a real
regression is slow in every pair, noise only has to miss one.  Raise
the factor to 1.5, which still rejects what the gate exists to catch
(a second enumeration or a quadratic, 2x and up).  The paced rounds
stay at three on the real clock.
2026-10-02 00:51:44 +02:00
LibretroAdmin 70a4c0d142 rmodtracker: apply IT S70-S76 past-note actions and NNA overrides
S7x was ignored for IT. Apply the note-related half as Impulse Tracker
does:

- S70, S71 and S72 cut, release or fade the notes this channel has
  moved to the background (fade approximated as a release, as for the
  new-note action);
- S73-S76 override the playing note's new-note action (cut, continue,
  off, fade) until the next note, which a portamento does not count
  as.

S77-S7C (switching the envelopes per note) are still ignored. The
three test files without S7x render byte-identical.
2026-10-02 00:38:21 +02:00
LibretroAdmin f87d06d2f0 rmodtracker: give IT volume-column slides their shared memory
Impulse Tracker's four volume-column volume slides (fine up, fine
down, up, down) share one memory per channel, and a slide of 0 recalls
it. rmodtracker treated 0 literally, so a run of B0 after B1 - a fade
written as one fine step down and then repeats - did nothing.
through_the_infinite.it fades six channels that way from 147 s to 158 s
and played them up to 14 dB loud there.

Keep the memory for IT; XM's volume column has none and is unchanged.
Against it2play the share of 50 ms windows more than 6 dB off drops
from 2.4% to 0.8% (OpenMPT: 1.8% to 0.5%); the other three test files
render byte-identical.
2026-10-02 00:38:17 +02:00
LibretroAdmin c5600b329a rmodtracker: load Impulse Tracker 1.x instruments
Files whose compatible-with version is below 2.00 store instruments in
the IT 1.x layout, but the loader always read the 2.x one: NNA, DCT,
DCA and fadeout came from the wrong bytes, the global volume was read
from where 1.x keeps its fadeout, and the volume envelope from where
1.x keeps an unused 200-byte table, so the envelope was garbage or
silent. Such songs played up to 27 dB quiet and largely wrong.

Read the 1.x layout when the header's cmwt is below 0x200, as Impulse
Tracker and it2play do: NNA and DCT at 0x1A/0x1B, fadeout at 0x18 in
half the 2.x units, the volume envelope as up to 25 tick/value byte
pairs at 0x1F8, and no DCA, instrument global volume, filter, pan or
pitch envelope. 2.x files are unaffected.

Tested on landing.it and through_the_infinite.it rewritten into the
1.x instrument layout, against it2play rendering the same files: the
share of 50 ms windows more than 6 dB off drops from 41.8% to 0.1% and
from 28.5% to 2.6% (the latter's original renders at 2.4%).
2026-10-02 00:27:50 +02:00
LibretroAdmin 2a1b412a3f netplay: drop core packets from non-playing clients instead of NAKing
The server answered NETPLAY_CMD_NETPACKET from a client that is not
playing with a NAK, which closes the connection. A client reaches that
state without misbehaving: it calls the core's netpacket start() at the
end of the handshake while still a spectator and only then asks to
play, so an older client whose core sends from start() or in the frames
before its promotion lands, or a client set to start as a spectator,
was disconnected.

Read the packet off the stream as before, then drop it without passing
it to the core or relaying it, so spectators still cannot reach core
packet parsers but stay connected. Logged at debug level, since a
spectator's core may send every frame.
2026-10-01 23:58:56 +02:00
Acts1631 e8c6c72cdb netplay: restrict core packets to playing clients
The server accepts NETPLAY_CMD_NETPACKET from connected spectators,
including clients authenticated with a spectator-only password. It
forwards their payloads to the core or other players, exposing core
packet parsers to unauthorized input.

Reject packets from non-playing clients before dispatch and prevent
spectator clients from sending core packets. Leave client reception
unchanged because cores can send during player admission before the
mode change reaches the client.
2026-10-01 23:38:43 +02:00
LibretroAdmin a5e5555bba vfs_smb: build against both libsmb2 share-enumeration APIs
libsmb2 reworked libsmb2-share-enum.h in place upstream (48e2461,
2026-10-01): the srvsvc_* structures, SHARE_INFO_1 and
SRVSVC_SHARE_TYPE_* became a flat struct smb2_share_enum_reply,
SMB2_SHARE_INFO_1 and SMB2_SHARE_TYPE_*. The Linux libsmb2 CI lane
builds libsmb2 from upstream master, so smb_enum_shares stopped
compiling there.

Map the names once, keyed on SMB2_SHARE_TYPE_DISKTREE, which only the
new header defines; the async call and the level-1 netname/type
fields are the same in both. Builds against the headers before and
after that commit, and with HAVE_RETROSMB, which does not use this
path.
2026-10-01 23:28:01 +02:00
libretroadmin c734bc66a6 tasks: a refused blocking task is freed and reported, never left pending
The task queue refuses a blocking task while another runs, and four
pushers ignored the refusal. content_load_state() had already marked the
load pending, so a load pushed while a save state, a screenshot or a
Wi-Fi or Bluetooth task ran left the flag set for the rest of the
session: closing content waited on it forever, and a movie recording
never started. It now clears the flag, frees the task and returns false,
which every caller already handles. The undo save state, the Wi-Fi and
the Bluetooth pushers leaked the refused task and reported success; they
free it and return false.

samples/tasks/save_state checks that a load pushed while a save runs is
refused and not left pending - both fail without this change - with
LeakSan covering what the refusal frees.
2026-10-01 22:41:22 +02:00
LibretroAdmin 76b0a01740 rmodtracker: play IT at Impulse Tracker's level
The IT loader scaled the mix-volume byte by 4/3 to sit on libxmp's IT
levels, which run about 2.5 dB over Impulse Tracker itself. Against
it2play and OpenMPT every IT file came out 2.2-3.8 dB hot, and loud
songs clipped: Deus Ex - The Illuminati hard-clipped 7065 samples
(OpenMPT: 430), Jazz Jackrabbit 2's Streetwize 1941 (44).

Map the mix volume straight onto the gain, honouring its full 0-128
range (128 was masked to 0 and played at the default). Four test
files now sit within 0.7 dB of it2play and OpenMPT, and clip about as
often as OpenMPT does (398 and 37 samples).
2026-10-01 22:40:45 +02:00
LibretroAdmin 7e91634b64 rmodtracker: clip the IT filter where Impulse Tracker does
IT2 runs its resonant filter on the raw sample, scaled to 15 bits, and
clamps each output to 16 bits before the voice volume applies, so a
resonant peak clips at twice the sample's full scale. rmodtracker
filters after the volume, with headroom that never clips, so high-Q
sweeps through a loud sample came out several dB over IT2 and OpenMPT:
through_the_infinite.it's sweeps peaked about 4 dB hot.

Clip both filter paths at twice the side's gain, which is where a
full-scale sample lands in the buffer. Against an it2play render the
share of 50 ms windows more than 6 dB off drops from 3.7% to 2.5%
(OpenMPT: 4.8% to 1.9%); files without filters are unchanged.

The float path also kept its memory in the integer fields scaled by
32768, which overflows once a clipped value passes 65535 buffer
units; give it float fields of its own.
2026-10-01 22:40:41 +02:00
libretroadmin 07fd591e49 win32 companion: hand Windows its text as UTF-16
The Win32 companion went through the ANSI entry points with the core's
UTF-8 strings, so on NT every name outside ASCII was read in the local
code page: CJK, Korean or Thai file names in the browser and playlists,
and translated labels in menus and titles, came out as mojibake, and a
path the code page cannot hold could not be opened, picked or dropped.
Its windows, controls, menus, list rows and columns, tabs, status text,
file and folder pickers, dropped files, icons and message boxes now go
through the W entry points, converting at the edge - the virtual list
filling the control's own buffer row by row - and Windows does the font
fallback. A Win9x build (LEGACY_WIN32) keeps the ANSI entry points and
converts to and from the local code page instead of passing UTF-8
through.

The Win32 companion test reads rows as UTF-16 and browses a directory
holding a file named in CJK, Hangul and Thai, which now shows as itself;
the test script runs Wine in a UTF-8 locale so the file can be made.
2026-10-01 22:27:10 +02:00
libretroadmin 7a55ed4b27 font_driver: map font files; archives extract by rename
Where there is mmap(), a font file is mapped rather than read whole: a
face's pages are read in as its glyphs are drawn and stay the file's,
so the 4.4 MB CJK fallback face costs what is drawn from it - 2 MB with
900 distinct characters on screen, less in a menu that shows a few.
Win32, the consoles and builds without mmap() read the file as before.
font_driver.c includes config.h first, as vfs/vfs.h - which
streams/file_stream.h pulls in - decides from HAVE_MMAP whether the VFS
maps at all.

A mapping does not survive its file being truncated, so archive
extraction now writes each member beside its file and renames it into
place, falling back to writing in place where a rename cannot be done.
An Update Assets run while fonts are in use leaves them reading the
file they mapped, and an extraction stopped part way leaves the old
file whole rather than cut short.
2026-10-01 22:27:05 +02:00
LibretroAdmin 68d4e69d95 rmodtracker: avoid left-shifting negative filter values
The integer resonant filter took its error term as acc - ( y << 14 )
and stored its clamped output back as y << 2, both undefined for a
negative y (UBSan flags them on any filtered IT). Multiply instead;
the result is the same on every target.
2026-10-01 22:25:48 +02:00
LibretroAdmin a212646e9b rmodtracker: raise the background voice pool to 64
New-note actions move continuing notes to a shared pool of background
voices, stealing the quietest when it is full. Songs with continue or
fade actions on many channels exhaust 32 voices: through_the_infinite.it
keeps around 14 voices per channel ringing, and with 32 the stolen
voices left parts of each phrase several dB short of Impulse Tracker.
At 64 its output stops changing (128 and 256 give identical renders),
for 32 more struct channel copies per replay.
2026-10-01 22:25:44 +02:00
LibretroAdmin 7ed6ce397b rmodtracker: fade IT instruments at IT's rate
IT subtracts the instrument fadeout from 1024 each tick (it2play and
OpenMPT both load the IT 2.x value as is). The engine fades from
32768, so the step is fadeout * 32, not * 64; instruments faded out
twice as fast as they should.
2026-10-01 22:25:41 +02:00
LibretroAdmin 7deaa8ebf9 rmodtracker: use IT's 0-128 global volume for Vxx and Wxy
The IT loader halved the global volume into the S3M/XM 0-64 range and
shared their effect handlers, so Vxx clamped at 64 (V40, half volume
in IT, played at full) and Wxy slid by XM rules: WF1, a fine slide
down by 1, became a slide up of 14 per tick. through_the_infinite.it
ends with WF1 / W00 to fade out and instead stayed at full volume.

Keep IT's global volume in 0-128 and scale it accordingly, let Vxx
set up to 128, and slide as IT does: W0y and Wx0 on the non-row ticks,
WFy and WxF once on the row.
2026-10-01 22:25:35 +02:00
LibretroAdmin 4091d61f47 rmodtracker: no IT fadeout while a sustain loop holds the envelope
IT starts the fadeout when a volume envelope reaches its last node,
but a sustain loop keeps it from getting there while the key is held,
even when the sustain point is the last node. rmodtracker only
excluded normal loops, so notes on such instruments faded out while
still held; channels 9-11 of through_the_infinite.it were silent for
most of the song. Exclude sustained envelopes too.
2026-10-01 22:25:32 +02:00
LibretroAdmin de44ef0f20 rmodtracker: restart IT instruments on notes without an instrument
Impulse Tracker starts a new voice for every note, whether or not the
row has an instrument number: the envelopes restart, the key goes down
again, fadeout resets and the instrument's initial filter applies.
rmodtracker did this only when an instrument was given, as XM does,
so on a run of bare notes the volume envelope ran out once and the
channel faded to silence for good. Channel 2 of through_the_infinite.it
was silent from 16 s to 56 s this way.

Do the reset for IT on any non-porta note without an instrument; the
volume still carries over, as in IT.
2026-10-01 22:25:28 +02:00
LibretroAdmin 7d03e1d428 rmodtracker: apply IT Zxx and SFx filter macros
The IT loader ignored Zxx, so songs that sweep the resonant filter
with it played unfiltered: through_the_infinite.it uses Zxx 1861
times and came out 12-17 dB too bright between 3 and 8 kHz.

Reduce the MIDI macros to the filter controls the engine has. Without
an embedded configuration Impulse Tracker's defaults apply: SF0
makes Zxx 00-7F set the cutoff, and Z80-Z8F set the resonance in
steps of 8. An embedded configuration (special bit 3, after the
optional edit history) is parsed for the same forms: "F0F000" for
the cutoff and "F0F001" for the resonance, followed by z or a fixed
hex value; other macros are ignored. SFx picks the macro per channel.
2026-10-01 22:25:23 +02:00
LibretroAdmin f57fa00c69 CI: samples/input: install mingw-w64 and Wine in the bounded deps step
The winraw_abs_pos_test step installed gcc-mingw-w64-x86-64 and
wine64 with a bare apt-get install: no timeout, no download-first
retry, and -qq hiding all progress. A stalled mirror left the step
silent until the job's 20-minute timeout cancelled the run.

Add both packages to the "Install dependencies" list so they go
through the same bounded, retried download as the rest, and drop the
in-step install. Also kill wineserver on exit so a lingering server
cannot keep the step's output open.
2026-10-01 22:04:35 +02:00
libretroadmin 83e76b961d ctr, gx2, rsx: let the glyph atlas grow; the 3DS uploads new glyphs
The 3DS swizzled the atlas into its font texture once, at init, and
never again, so every glyph rasterized afterwards - all fallback-font
text, and whatever took a cell another glyph had held - drew from cells
the texture never received. Each message now swizzles the atlas's dirty
rectangle into the texture first. The 3DS, Wii U and PS3 drivers then
let the atlas grow - to 1024, 1024 and 2048 px - making texture memory
of its new size, and for the Wii U a texture-size block too, and keeping
what it replaces until the font is freed: a frame in flight may still
draw from it, and freeing it there means waiting for the GPU, which
none of them does for this. The atlas growing at most twice keeps that
to a third more memory. The 3DS's never-enabled VRAM variant of the
texture goes.
2026-10-01 21:42:56 +02:00
LibretroAdmin b63c4cb097 rmodtracker: limit XM sample sizes to the data left in the file
After the INT_MAX check, an XM sample header could still claim close
to 2 GB of data. A small file could make the loader allocate ~4 GB of
silence, and once that succeeded, offset + count in data_sam_s8 (and
offset + count * 2 in data_sam_s16le) overflowed, so their end-of-data
clamp failed and they read past the file buffer; the running offset
sum after each sample could overflow as well.

Clamp the sample size to the bytes remaining after the sample data
offset, counting the ADPCM table and two samples per byte for ModPlug
ADPCM. Samples that fit in the file are unchanged; a truncated file
now gets a sample ending at the end of the file rather than one padded
with silence.
2026-10-01 21:38:19 +02:00
LibretroAdmin e5bf4c893e rmodtracker: reject negative IT sample loop starts
The IT loader bounds the frame count, but reads the loop start into a
signed int and only checks that the loop end lies after it and within
the sample. A loop start of 0x80000000 or above becomes negative and
passes, leaving the loop sentinel copy and the mixer reading before
the sample buffer. Drop the loop when its start is negative.
2026-10-01 21:38:15 +02:00
LibretroAdmin 3650a9e270 rmodtracker: bound S3M sample length and loop fields
The S3M loader read the 32-bit sample length, loop start and loop end
into signed ints. Large values turned negative and passed the loop
checks: a length of 0xFFFFFFFF with a looped sample left loop_start at
-1, so after calloc(0) the loop sentinel was written before the sample
buffer, and a negative loop start with a small loop end left the loop
indexing before the buffer at load and during playback.

Keep the fields unsigned until they are bounded, limit the length to
0x1000000 frames as the IT loader does, clamp the loop end to the
length and drop the loop when it is empty. Well-formed files load the
same loop as before.
2026-10-01 21:38:12 +02:00
libretroadmin 060a5a297e gdi, switch, ps2, vita: let the glyph atlas grow
GDI already made its atlas DIB again when the atlas's size changed, and
the Switch blits glyphs from the atlas in memory; both offer it room to
grow and ask for it before drawing. The PS2 makes its EE copy of the
atlas again at the new size and has the GS sent it again, as it does
for the menu texture, with the atlas kept to 512 px for the GS's 4 MB.
The Vita makes a texture at the new size and keeps the one it replaced
until the font is freed - a frame in flight may still draw from it, and
freeing a texture there means waiting for the GPU - which the atlas
growing at most twice keeps to a third more texture memory.
2026-10-01 21:35:31 +02:00
Acts1631 7608a704c1 rmodtracker: bound XM sample loop indexes
XM loop fields are read into signed ints. An oversized loop length can
become negative and pass the end-only check, causing a heap write
before the sample buffer when the loop sentinel is copied.

Keep the fields unsigned until they are checked against the sample
length. Reject sample sizes that do not fit the decoder's signed
counts, and guard ping-pong loop doubling against integer overflow.
2026-10-01 21:38:18 +02:00
LibretroAdmin 1c08bce61f connmanctl: quote the tether AP name and password
connmanctl_tether_toggle pasted the AP name and password from the
user's tether config unquoted into a popen() command, so a space,
quote or ';' in either one split the arguments or ran as shell syntax.
Pass each as a single-quoted word, with embedded quotes escaped, and
refuse to run a command that would not fit rather than truncate it.

Turning tethering off no longer appends the two empty arguments the
callers pass; the command was already "connmanctl tether wifi off"
once the shell dropped them. Also stop on a failed popen().
2026-10-01 21:27:15 +02:00
LibretroAdmin a2b9374396 connmanctl: check and free the service name scratch buffer
connmanctl_get_connected_servicename used its malloc'd line buffer
without checking for NULL, and only freed it when an online service
was found; when the loop ran out of services the buffer leaked. Bail
out on a failed allocation and free the buffer on the not-found path.
2026-10-01 21:27:11 +02:00
LibretroAdmin 0fc546c074 connmanctl: share the final return in the service ID check
connmanctl_valid_wifi_service_id returned false from inside its loop
on a disallowed character and again after it when no terminator was
found. Break out of the loop instead so both cases use the single
return false below it. No change in behaviour.
2026-10-01 21:27:08 +02:00
libretroadmin db681b076d d3d12, d3d8, d3d9, metal, software drivers: let the glyph atlas grow
D3D12 makes its font texture again when the atlas grows and retires the
old one behind the queue fence - the value the frame being recorded
signals at its end - releasing it and its descriptor once the fence has
passed, so nothing waits on the GPU and no command list in flight loses
what it names. D3D8 and both D3D9 drivers already remade their texture
when the atlas's size changed; they offer the device's largest texture
and ask for the atlas before drawing. Metal makes its managed buffer and
the texture on it again, a command buffer in flight keeping the old pair
alive. The SDL1, Xv, Exynos, OMAP and OGA drivers blit glyphs straight
from the atlas in memory and only offer it room to grow.
2026-10-01 21:23:44 +02:00
libretroadmin 388637b6c4 gl1, sdl2, sdl3, d3d10, d3d11: let the glyph atlas grow
The GL1, SDL2, SDL3, D3D10 and D3D11 drivers offer their fonts' glyph
atlas room to grow, up to the largest texture each can make - from
GL_MAX_TEXTURE_SIZE, the renderer's reported limit, D3D10's, and
D3D11's for its feature level - and ask for the atlas before laying a
message out, remaking their texture when it has grown: GL1 respecifies
its texture, the SDL drivers already remade theirs at the atlas's size,
and D3D10 and D3D11 release and make it again, the device keeping the
old one alive for draws already issued. On GL1 and SDL2, thirty titles
of thirty CJK characters each in XMB's History now draw every glyph
where SDL2 drew the stand-in past the 256th.
2026-10-01 21:12:21 +02:00
Acts1631 444f987961 connmanctl: validate Wi-Fi service IDs before shell use
ConnMan prints discovered SSIDs directly in its service list. An SSID
with embedded newlines can forge a row whose final token is treated as
a service ID. Connect or disconnect then interpolates that token into
a popen() command, allowing a nearby AP to execute shell commands.

Accept only bounded Wi-Fi service IDs made of ASCII letters, digits,
and underscores. Check IDs again at command sites, including names
found in the settings directory.
2026-10-01 21:15:05 +02:00
Anthony SchemelandClaude Opus 5.5 9a64f3fa3c webdav: a fresh client nonce for each digest challenge
webdav_create_digest_auth set cnonce to the constant "1a2b3c4f" for
every login. The cnonce is the client's own input to the digest
response; with it fixed, a hostile server (or anyone in the middle of
plain HTTP) chooses every other input and can test password guesses
against precomputed tables.

webdav_create_cnonce fills a 33-byte buffer in the state with 16 bytes
from crypto_random_bytes as hex, or, without HAVE_CRYPTO or when no
entropy source answers, an MD5 of the microsecond clock and a counter.
The buffer replaces the const char pointer, so nothing is allocated.

Tested against a local server that checks the digest response: before,
every login sent cnonce=1a2b3c4f; after, each run sends a new 32-digit
cnonce, and the server accepts the response both times and the sync
completes. Builds clean with make; webdav.c compiles as gnu89 with and
without HAVE_CRYPTO.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-01 21:04:31 +02:00
LibretroAdmin b181eec6cc gl: drop benchmark figures from the gl_glsl_set_vbo comment
The comment added with the GL_STREAM_DRAW change quoted a single
radeonsi measurement. Those figures will go stale and are already
recorded in that commit's message; keep only the reason for the hint.
2026-10-01 20:57:59 +02:00
Anthony SchemelandClaude Opus 5.5 f79bf6ec22 gl: upload per-draw coords with GL_STREAM_DRAW
gl_glsl_set_vbo re-specifies the GLSL backend's vertex buffer with
glBufferData on every draw whose coords changed - each font flush and
menu quad, many times a frame - but declared it GL_STATIC_DRAW, the hint
for data written once and drawn many times. GL_STREAM_DRAW is the hint
for this pattern; the glcore driver's scratch VBOs already use it.

Measured: Ozone's idle menu on the gl driver spends 2.7% of its time in
this upload (perf, radeonsi). A windowless EGL bench on the same GPU
(RX 6600, radeonsi) of the font path's pattern, twelve ~6 KB uploads
and draws a frame: STATIC 278 us/frame wall, 99 us CPU; STREAM 228-236
us wall, 84-87 us CPU, in two runs. GL_DYNAMIC_DRAW measured like
STATIC. The hint changes no results, only placement.

Checked: the Ozone menu runs on the gl driver (Xvfb, llvmpipe) with no
GL errors.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-01 21:00:17 +02:00
Anthony SchemelandClaude Opus 5.5 22c9d145f3 task_screenshot: initialise new_screenshot_dir before it is tested (#19667)
screenshot_dump only writes new_screenshot_dir when screenshot_dir is
set, then tests !*new_screenshot_dir to fall back to the content
directory. With the screenshot directory left at its default (empty),
that test reads uninitialised stack, so the screenshot can go to a
directory named from leftover bytes, which path_mkdir then creates.

Repro: null drivers, screenshot_directory = "", content loaded
(fceumm, an NES zip), --max-frames=30 --max-frames-ss. Under valgrind,
master reports 55 errors in 35 contexts, all in screenshot_dump
(conditional jumps, mkdir/openat on uninitialised bytes), and the PNG
was written to a new directory named with garbage bytes. With this
change: 0 errors, and the PNG lands beside the content.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-01 20:55:32 +02:00
libretroadmin 595257a21c font_driver, gl, glcore, vulkan: grow the glyph atlas a frame needs
A font's glyph atlas grows when a frame needs more glyphs than it has
cells for: at the next frame, before anything is drawn from it, its
columns and rows double - to 1024 cells, then 4096 - with every cell
staying where it was, so no glyph's offsets change. A video driver
offers it by setting the atlas's max_width and max_height to the largest
texture it can make and asking for the atlas before laying a message
out; it then remakes its texture at the new size. The GL, GL core and
Vulkan drivers do - GL respecifying its texture, GL core making a new
immutable one, Vulkan making a new staging and sampled pair and parking
the old one for the frames still drawing from it. Thirty titles of
thirty CJK characters each, in XMB's History, draw every glyph on all
three; a driver that has not opted in keeps the atlas at its first size
and draws the stand-in past it, as before.

The font_lifecycle frame test covers the growth: deferred to the next
frame, every earlier glyph in place, then the whole frame drawn, and a
second growth past 1024 cells.
2026-10-01 20:50:43 +02:00
libretroadmin f0abc20a4f samples/font_lifecycle: declare the read length at the top of its block
The stub's real-file branch declares its length before the read log
is taken under its lock, so the block opens with its declarations as
the coding guidelines ask.
2026-10-01 20:41:17 +02:00
libretroadmin c7221cae58 font_driver: keep a frame's glyph cells for that frame
A glyph cache hands out its least recently used cell for a new glyph,
and once a frame drew more distinct glyphs than the cache holds - 256 a
font - it handed out cells that glyphs earlier in the same frame were
already queued to draw from, so those drew as other characters, and
every glyph past the 256th was rasterized again and the atlas uploaded
again every frame. A cell looked up in the current frame is now left
alone until the next one begins: past what the cache holds, the rest of
a frame's glyphs go undrawn - their stand-in is drawn - and nothing is
rasterized again until the text changes. 400 distinct CJK glyphs a frame
cost 27 us instead of 1.4 ms, and nothing is drawn from a cell that has
changed under it.

font_driver_frame_begin() marks a frame: the video thread marks it
before the driver's frame when video is threaded, video_driver_frame()
otherwise. Until the first mark a cache is plain LRU, as it was.
samples/gfx/font_lifecycle gains a test of it.
2026-10-01 20:27:10 +02:00
libretroadmin e771f60819 samples/font_lifecycle: keep the read log under a lock
The fallback tests read fonts on threads of their own, and the stub's
record of which files were read was written by two threads at once in
the fallback test, which TSan reports. It is kept under a mutex, and the
chain test asks the stub whether a file was read instead of walking the
record itself.
2026-10-01 20:27:06 +02:00
libretroadmin 5829a0b7f3 font_driver: draw Ethiopic from its own fallback font
Ethiopic, its supplement and its extensions are drawn from
ethiopic-fallback-font.ttf in the assets' pkg/ when the font has no
glyph for them; the general fallback has none. The syllabary needs no
shaping, so the face renders it correctly a glyph at a time. Assets
without the file leave Ethiopic as undrawn as it was. The chain test
checks Ethiopic is looked for in its own face.
2026-10-01 19:40:45 +02:00
libretroadmin b29de508db font_driver: a second fallback face where the first lacks a range
A codepoint the font has no glyph for is drawn from the fallback face
its block picks, and where that face has none either, from a second one
known to carry the range with the fallback fonts the assets ship:
Hangul jamo, circled numbers and vertical and small form variants from
the CJK face, circled Hangul from the Korean one, Yijing hexagrams from
the general one. That is close to 400 more glyphs drawn - the circled
numbers in Japanese titles among them - and a codepoint no face is
known to carry still has no other file read for it.

samples/gfx/font_lifecycle gains a chain test, DejaVu Sans Mono standing
in for the general face and DejaVu Sans for the CJK one, run under ASan
and TSan in Linux-samples-gfx.yml.
2026-10-01 19:17:37 +02:00
libretroadmin c454fce0b0 gfx: resolve a font draw's parameters in one place
font_driver_resolve_params() fills in what a video driver's font draws
a message with: the caller's font_params, or for the on-screen message
the position and colour from the settings, left aligned, full screen,
opaque, under the usual 2 px shadow at 0.3 of the colour - with the
colour both as floats and as bytes, and the caller's float colour passed
through. Every video driver that drew from font_params resolves them
there instead of copying them field by field and reading the settings
itself; what is each driver's own stays with it - its packed colour
layout, the "alpha 0 means opaque" fix-up where it had one, and the 3DS
and PS2 drivers' own shadow for the message - and every driver ends up
with the same values as before. SDL2's and the Switch's on-screen
message take its position, and SDL2 its colour and shadow, from the
settings as the others do, where both drew it at the screen's corner.

The compile matrix's clang font layout lanes reject a declaration after
a statement, apart from the PS2 and Wii U drivers' C99 toolchains.
2026-10-01 19:07:20 +02:00
Anthony SchemelandClaude Opus 5.5 6ab85b6f5f core_option_manager: size the categories array by category count (#19668)
core_option_manager_new allocated opt->cats with calloc(_len, ...),
_len being the number of options, then filled it by category count.
A core declaring more categories than options writes past the end of
the heap block.

Repro: a minimal test core passing RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2
with four categories and one option, null drivers, under valgrind:
master reports 35 errors in 13 contexts, all invalid writes from
core_option_manager_new; with this change, 0. Every read of opt->cats
is bounded by cats_size. fceumm, which uses categories, loads with no
valgrind errors.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-01 18:41:43 +02:00
libretroadmin de9096b0cf vulkan: declare before the font layout's unused read
The line hook read its unused glyph count ahead of a declaration, which
the C89 lane rejects; the read follows the declarations.
2026-10-01 18:21:35 +02:00
libretroadmin 6bf58823c6 gfx: one text width measurement for every video driver
font_renderer_get_message_width() in font_driver.c measures text through
a font's glyph cache - the UTF-8 walk, the lookup with its '?' stand-in
and the summed advance - and every video driver's get_message_width
returns it, where each walked the text itself. Its loop is
gfx/font_measure.h, a statement block in the manner of
gfx/font_layout.h; Vulkan and Metal, which upload an atlas cell as each
glyph is looked up, include it with that upload as their hook, so no
loop pays a call per glyph. The widths and uploads are the same as
before; the 3DS driver no longer dereferences its font before checking
it.
2026-10-01 08:36:26 +02:00
libretroadmin 1923afac28 gdi: draw text through the shared font layout
The GDI driver's font lays text out through gfx/font_layout.h, which
leaves no video driver with a text layout of its own. The layout block
splits the message into lines and hands each one over, through
FONT_LAYOUT_SKIP, to be drawn as its shadow run and then its text run,
so the text still lands on the shadow; a run walks its glyphs through
the block too. Untinted text is AlphaBlended a glyph at a time as
before, and tinted text baked into the scratch DIB and blitted per line;
a tinted line that is also right or centred is measured once rather than
twice. The Windows 95 path, which hands the line to TextOut, is as it
was, and the calls into GDI and the scratch contents are the same as
before.

The layout block documents fl_m, the current line's first byte, as
readable from its hooks, which is how a driver drawing a line in passes
of its own gets the line. The compile matrix's clang font layout lanes
cover GDI.
2026-10-01 08:23:04 +02:00
libretroadmin b14d4982fb sdl, xvideo, exynos, omap, oga: decode on-screen messages as UTF-8
The software drivers that blit their own on-screen messages walked them
a byte at a time, so a multi-byte character came out as one Latin-1
glyph per byte - nothing a fallback font could help with - and a line
break was looked up as a glyph. They lay messages out through
gfx/font_layout.h instead: UTF-8, a codepoint the font cannot draw shown
as '?', and each line one line height below the last. Each blit is
unchanged; a glyph left out at the screen's edge now advances the pen,
where the next one used to be drawn in its place.

The OGA driver hung on a codepoint its font had no glyph for, never
stepping past it, and wrote past its message surface once the text
filled it. It wraps a glyph before it would cross the right edge, draws
nothing once a row would cross the bottom, and reports no more height
than the surface has.
2026-10-01 08:12:38 +02:00
libretroadmin f2e8be023f metal: draw text through the shared font layout, and compile it in CI
The Metal driver's font lays text out through gfx/font_layout.h, one
method where it had a per-line method, a wrapper splitting the lines and
a width method called per line for right and centred text. It keeps its
pixel-snapped line origin, its six-vertex quads in the vertex range and
its atlas-cell update after every glyph lookup, the measuring pass's and
the '?' stand-in's included. The message length is passed down rather
than rescanned, and the vertices and cell updates are the same as before.

tools/platform_stubs/metal stands in for the parts of the macOS SDK the
driver uses - Foundation, AppKit, Metal, MetalKit, QuartzCore, simd - in
the shapes the SDK gives them, and the compile matrix compiles
gfx/drivers/metal.m as Objective-C against them with clang. A selector
the stand-ins do not declare is an error there, so a new Metal call
needs its declaration added with it.
2026-10-01 08:01:00 +02:00
libretroadmin 569b0598c8 sdl3: draw text through the shared font layout
The SDL3 driver's font lays text out through gfx/font_layout.h, as SDL2's
does: one function where it had a per-line function and a wrapper
splitting the lines, keeping its top-left pixel origin, floating-point
pen, atlas upload at the start of each line and SDL_RenderGeometry
batches of up to 64 glyphs on the stack. A right or centred line is
measured in the layout's own pass rather than by its width function,
with the '?' stand-in known before it, and the vertices and draws are
the same as before.

The compile matrix's clang font layout lanes cover it where the SDL3
headers are installed.
2026-10-01 07:51:16 +02:00
libretroadmin 0b323245b8 d3d9: draw text through the shared font layout
Both D3D9 drivers' fonts lay text out through gfx/font_layout.h. Each
looks a glyph up once for a line's shadow run and its foreground run,
drawn in that order as before, where it used to measure and walk the
line again for each run, and the draw both runs repeated is a function
of its own. Empty lines are passed over before any lookup, as they were.
The vertices and draws are the same as before.

The compile matrix's clang font layout lanes cover both.
2026-10-01 07:44:39 +02:00
libretroadmin f884160138 gfx: look nothing up for a line a driver will not draw
D3D10, D3D11, D3D12, the Wii U and the Switch leave out a line too long
for their buffer, or past their limit, and before drawing through the
shared font layout they never looked its glyphs up. Through the layout
they did, measuring and walking a line they then skipped - rasterizing
glyphs nobody would see and evicting ones that were in use. Each now
names the line in FONT_LAYOUT_SKIP, so it is passed over before any
lookup, as it was.
2026-10-01 07:39:11 +02:00
libretroadmin 3f6df7b067 font_driver: the fallback file table and its references only with the loader
The fallback fonts load on a worker that needs threads and a lock-free
compare-and-swap. Where the atomics backend has none (the PS2 one), the
loader was compiled out but its file table and reference slots were not,
and -Werror stopped the atomic-backend lane on them. FONT_FALLBACK_LOADS
is now decided before both and guards them with the loader.
2026-10-01 07:38:42 +02:00
libretroadmin 23a9d859d6 CI: the Vulkan font layout lane on the tree's own Vulkan headers
The lane compiled gfx/drivers/vulkan.c against the system's
<vulkan/vulkan.h>, which the runner does not have, and failed there. It
now takes the Khronos headers the tree carries in gfx/include, as the
real builds do, and runs on any runner.
2026-10-01 07:27:28 +02:00
libretroadmin 224aa0d30f ctr, rsx, d3d8: draw text through the shared font layout
The 3DS, PS3 and D3D8 drivers' fonts lay text out through
gfx/font_layout.h. The 3DS keeps its vertex cache, its capacity check
ahead of each line and its per-line draw, now a function of its own; the
PS3 keeps its integer line origin, six-vertex quads and 64-glyph chunked
draws. D3D8 looks each glyph up once for a line's shadow run and its
foreground run, drawn in that order as before, where it used to measure
and walk the line again for each; its scissor cull now runs before the
line is measured, so a line out of view is not looked up at all. All
three produce the same vertices and draws as before.

The layout block takes an optional FONT_LAYOUT_SKIP, for a driver that
can tell a line will not be drawn before looking any of it up. The
compile matrix's clang font layout lanes cover D3D8.
2026-10-01 07:25:15 +02:00
libretroadmin fdbcf76eb6 sdl2: look the '?' stand-in up before laying a line out
A right or centred line is measured before its first glyph is drawn, so
the glyph that stands in for a codepoint the font cannot draw has to be
known by then. It was looked up as each line started, after the
measuring pass: the first such line was measured without it, and the
text drawn off by the stand-in's advance. It is looked up once, ahead of
the layout.
2026-10-01 07:25:11 +02:00
libretroadmin e8260a749f ps2, switch, gxm, gx2: draw text through the shared font layout
The PS2, Switch, Vita and Wii U drivers' fonts lay text out through
gfx/font_layout.h, one function where each had a per-line function and a
wrapper splitting the lines. Each keeps its own line origin and the way
it draws a glyph: PS2 its per-line alpha state and gsKit sprites, the
Vita its atlas copy and tinted draws, the Wii U its vertex cache, its
truncating pen and its per-line draw and capacity check, the Switch its
software blit and its line-length limit. The text length is passed down
rather than rescanned.

The Switch blit counted rows with the line's own y origin, so every glyph
after the first on a line was drawn at the height the previous one ended
on; it has counters of its own. The compile matrix's clang font layout
lanes cover the four, and the Vita driver no longer leaves its shader
patcher's result set but unread.
2026-10-01 07:16:15 +02:00
libretroadmin 4b32cdafc5 gl1, sdl2: draw text through the shared font layout
The GL1 and SDL2 drivers' fonts lay text out through gfx/font_layout.h,
one function where each had a per-line function and a wrapper splitting
the lines: GL1 keeps its integer line origin, six-vertex quads and
64-glyph chunked draws; SDL2 keeps its top-left pixel origin, its
floating-point pen, its atlas upload at the start of each line and its
SDL_RenderGeometry batches. The text length is passed down rather than
rescanned, SDL2 measures a right or centred line in the layout's own pass
instead of calling its width function per line, and both draw the same
pixels as before.

The compile matrix's clang font layout lanes cover both.
2026-10-01 07:08:36 +02:00
libretroadmin 5d5e3ebf0d d3d10, d3d12: draw text through the shared font layout
D3D10 measures a line with a shadow or an alignment first, then writes
each glyph once as its shadow and its foreground sprite, straight to
their slots - the line's shadows ahead of its glyphs, as before - where
it used to measure and walk the line again for each of the two. D3D12
keeps its single interleaved pass and its retroactive shift of right and
centred lines; the line splitting, glyph lookup and '?' stand-in are the
shared block's. Both keep their own truncating pens and their rule for
lines too long for the buffer, and write the same sprites as before.

The compile matrix's clang font layout lanes cover both.
2026-10-01 07:00:36 +02:00
libretroadmin 1f7be4e209 gfx: no set-but-unused warnings from the shared font layout
A driver that draws through gfx/font_layout.h but has no use for one of
the values it reports - a line's glyph count for Vulkan and the GL
drivers, the width and the shared pen for D3D11, which keeps its own -
reads it as void in its macro, so GCC 15 and clang no longer warn that
the layout's counter is set but never used.

The compile matrix builds each driver drawing through the layout with
clang and the warning made an error.
2026-10-01 06:55:20 +02:00
libretroadmin 4950ba810d d3d11: draw text through the shared font layout
The layout block also reports each line's glyph count and length. D3D11
measures a line first when it has a shadow, so every sprite is written
straight to its slot - the line's shadows ahead of its glyphs, as before
- instead of emitting the glyphs, cloning them and swapping the two
runs; right and centred lines are still shifted once their advance is
known, with their shadows taken from the shifted glyphs. The sprites are
the same as before, byte for byte, and lines too long for the buffer
are still left out.
2026-10-01 06:49:34 +02:00
libretroadmin adc900bdbf gl3: build without slang again
The GL core driver's scRGB/HDR10 state lives outside the slang guard,
and what needs the slang pipelines is inside one: the context's HDR
framebuffer is taken up only with slang, the HDR10-to-SDR downconvert
and the end-of-frame scRGB encode are compiled only with it, and the
ribbon VBO helper only where its caller is. A build without slang - the
C89 lane is one - compiles again, and draws SDR as it always has.

The compile matrix checks gl3.c both with and without slang.
2026-10-01 06:39:41 +02:00
libretroadmin 7101c67f85 gl3: draw text through the shared font layout
The GL core driver's font lays text out through gfx/font_layout.h, as the
Vulkan and GL drivers do: the line splitting, glyph lookup, alignment
width and pen advance are the shared block's, and the driver keeps its
own line origin, its six-vertex glyph quads and its 64-glyph chunked
draws. The text length is passed down rather than rescanned, and the
output is the same pixels as before.
2026-10-01 06:28:28 +02:00
libretroadmin ed4731f410 unix: reap the narrator with waitpid instead of ignoring SIGCHLD
After the first narration, SIGCHLD was set to SIG_IGN for the whole
process, so every later wait on a child - nmcli's connect and
disconnect among them - failed with ECHILD and read as failure. The
narrator stays a direct child and is reaped with waitpid(WNOHANG):
is_narrator_running_unix() reaps it once it has exited, so a finished
narrator never reads as running and its pid is not kept past its exit,
and one sent SIGTERM that has not exited yet is kept to reap on a later
call. Nothing waits, and SIGCHLD keeps its default action. As found in
#19647, which reparented the narrator instead; a finished narrator then
read as running until whatever adopted it reaped it.
2026-10-01 06:26:05 +02:00
libretroadmin c3c7efa427 gfx: one text layout for the video drivers' fonts, Vulkan and GL first
gfx/font_layout.h is the text layout every video driver's font repeats:
splitting lines, walking the UTF-8, looking each codepoint up with '?'
standing in for one the font cannot draw, measuring a line for right and
centred alignment, and advancing the pen. It is a statement block that a
driver includes in its own render function, with macros for what is the
driver's own - where a line starts, how a glyph's quad is written in its
vertex format, and when its atlas is uploaded - so the loop compiles to
what the driver wrote out by hand, with no call or buffer per glyph.

The Vulkan and GL drivers draw through it, and produce the same pixels as
before. GL measuring a line for alignment now counts '?' for a codepoint
the font cannot draw, as drawing it does.
2026-10-01 06:06:40 +02:00
libretroadmin be1e188d09 CI: the CoreText rasterizer in an Apple shape; fallback fonts under TSan
The compile matrix checks gfx/drivers_font_renderer/coretext.c and the
font driver's Apple branch, macOS and iOS, against stand-ins for the
CoreFoundation, CoreGraphics and CoreText declarations they use, so a
change to the rasterizer interface is caught before the Apple jobs.

samples/gfx/font_lifecycle gains a fallback test: two fonts with no
glyph for a set of codepoints, on two threads, ask for the same fallback
face at once, with real stb and real rthreads behind font_driver.c. The
file has to be read once and every glyph has to arrive. It runs in the
ASan/UBSan check and again under TSan in Linux-samples-gfx.yml.
2026-10-01 05:48:23 +02:00
libretroadmin f66810d639 font_driver: one glyph cache for every rasterizer, with fallback faces
The glyph cache - the 16 x 16 cell atlas, the codepoint hash, least
recently used eviction and the dirty rectangle - lives in font_driver.c,
and stb, freetype and CoreText are rasterizers behind it: they open a
face on bytes font_driver.c read for them, say whether it has a glyph
for a codepoint, and draw that glyph into the cell they are handed. The
video drivers keep calling get_glyph and get_atlas as before; a hit
costs the same hash walk it did in each renderer's own copy.

A codepoint the font has no glyph for is drawn from a fallback face
picked by its Unicode block - the CJK, Korean, Thai and general fallback
fonts in the assets' pkg/ directory - so a Chinese, Korean or Thai file
name shows in an English menu instead of boxes. Each fallback file is
read the first time a codepoint needs it, on a thread of its own, and
shared by every font afterwards; until it arrives that codepoint draws
blank and is not cached. A font that never meets such a codepoint reads
nothing and keeps nothing more.
2026-10-01 05:36:31 +02:00
Acts1631andlibretroadmin 8e0f4ae0ac nmcli: avoid shell execution from nearby SSIDs (#19646)
* wifi: pass discovered SSIDs to nmcli without a shell

The nmcli scan path interpolated each nearby SSID into a system()
command. A nearby access point could execute commands as the
RetroArch user when the Wi-Fi network list was opened.

Query saved profiles and connect or disconnect using argument-based
nmcli calls, so SSIDs and passphrases remain literal arguments.

* wifi/nmcli: fill the nmcli argument vectors by assignment

C89 takes only constant expressions in an aggregate initializer, and the
argument vectors held the SSID and passphrase: the C89 lane rejected all
five. The vectors are now declared and filled in by assignment.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-10-01 05:32:26 +02:00
LibretroAdmin c4ff3d3369 samples/tasks/http: link compat_strl.c into http_dns_lifetime_test
9b5f6ed768 ("net/tls: certificates for IP addresses; no server name
for them") made net_http_connect() copy the TLS host name with
strlcpy(). compat/strl.h maps strlcpy to strlcpy_retro__, which lives
in compat_strl.c, but Makefile.dns_lifetime never linked that file.
The non-SSL build drops the call, so only the HAVE_SSL build failed
to link:

  undefined reference to `strlcpy_retro__'

Add compat_strl.c to the link line. The test passes again with
SSL=1, SSL=0 and SANITIZER=address,undefined.
2026-10-01 05:18:36 +02:00
Acts1631 f20adf39f8 bluetoothctl: validate addresses from device listings (#19651)
A nearby Bluetooth name can contain a newline that forges a line in
bluetoothctl devices output. The next status check or a connect action
used the forged line's second token in a popen() command, allowing
shell commands to run as the RetroArch user.

Accept only lines with a valid Bluetooth address, use that address
for status, connect, and remove commands, and cap the list to the
fixed status arrays.
2026-10-01 05:19:08 +02:00
libretroadmin 6d481880de menu, widgets: reload assets in place, releasing what was loaded
A menu driver's context_reset loads every texture into its slot without
looking at what the slot held, so calling it on a live menu leaks the
whole icon set already loaded. menu_driver_context_rebuild() runs
context_destroy first and then context_reset against the running video
driver - the pair the menu goes through when it survives a video driver
swap - and the in-place resets use it: the Dynamic Wallpaper, Show
Sublabels and XMB Entry Icons settings, Start on the wallpaper entry,
and Update Assets.

Update Assets also reloads the notification widgets with
gfx_widgets_reload_assets(): the message queue and volume icons are
reloaded under the widget state lock, asynchronously so nothing waits
on the video thread while the lock is held, and the next layout pass
rebuilds every widget font on the thread that drives the widgets,
retiring the old ones, so the fonts from the new assets replace the
built-in one the widgets fell back on.
2026-10-01 04:56:43 +02:00
LibretroAdmin e993371eb9 camera: the frame_work bit is the whole test; camera_allow drops it through a command
The camera poll in runloop_iterate() tested RUNLOOP_WORK_CAMERA and
then re-tested five more things every frame: settings->bools.
camera_allow, cb.caps, driver, driver->poll, data. The bit is meant to
mean "poll this every frame", so those conjuncts belong at the edge
that raises it, not on the frame path.

driver_camera_start() now raises the bit only when the start
succeeded, the driver can be polled, and the core registered caps
(data and driver are preconditions of reaching start at all). Stop
and teardown already drop it. The one condition that could change
underneath - the user flipping camera_allow off mid-session - gets
its own edge: the setting row's command trigger fires
CMD_EVENT_CAMERA_ALLOW_APPLY, which stops a running camera (and so
drops the bit). Allowing again does nothing by itself, as before: the
core has to call start() for the camera to come up.

A trigger rather than a change handler because the generated
settings glue (msg_hash, configuration.c, the string passes) only
defines the base S_BOOL variant for this row; other rows already pass
a CMD_EVENT_* through the same consumers, which discard it.

Two visible changes: a disallowed-while-running camera is now
actually stopped instead of merely no longer polled while still
capturing, and the frame path is one bit test. Frontend-overhead
harness passes.
2026-10-01 04:55:14 +02:00
libretroadmin 280094adb9 netplay: the password salt from the entropy source; hashes compared in constant time
The password challenge salt came from an LCG seeded with time(NULL):
anyone who saw one salt could predict every later one. It now comes from
crypto_random_bytes(), the platform entropy source on Windows, UWP,
POSIX and the 3DS, Vita and Switch; builds without the crypto library,
or a platform without a source, keep the LCG seeded from the
microsecond clock and the connection's address. The client's password
hash is compared with crypto_memeq_ct() rather than memcmp(), whose
early exit told how much of it matched. The salt travels in the same
field, so older clients are unaffected. As found in #19649.
2026-10-01 04:53:35 +02:00
Anthony Schemel 61b0a7eb28 config: re-apply tls_verify_mode on every settings load (#19648) 2026-10-01 04:43:43 +02:00
libretroadmin d66d06f85a crypto: NEON ChaCha20 on 32-bit ARM too; the crypto suite on 32-bit ARM in CI
The four-block NEON ChaCha20 ran on AArch64 only, though the Vita and
32-bit Android have NEON as well and are where ChaCha20-Poly1305 is now
negotiated first. It now runs wherever NEON is: ChaCha20-Poly1305 on
ARMv7 takes 14.7 instructions an octet, down from 32.

No job ran the crypto suite on 32-bit ARM, the consoles' architecture.
crypto-arm-check runs it under qemu-arm in their two shapes, ARMv7-A
with NEON as on the Vita and ARMv6K with VFP only as on the 3DS, where
every portable path is taken, and the libretro-common job runs it.
2026-10-01 04:20:48 +02:00
LibretroAdmin 9b5f6ed768 net/tls: certificates for IP addresses; no server name for them
A TLS connection to an IP address - https://[::1]:8443/ or
https://192.0.2.1/ - was treated like one to a host name, which is
wrong in three places:

- net_http handed the TLS layer the host as it appears in the URL, so
  an IPv6 literal kept its brackets ("[::1]"), which nothing can match.
  The brackets belong to the URL and the Host header only; the TLS
  layer now gets the bare address.
- The built-in client sent the address as server_name. RFC 6066
  allows host names only there; for an IP address the extension is now
  left out.
- x509_match_hostname() compared an address against dNSName entries
  and the commonName as text, so a name certificate whose CN or a DNS
  entry happened to read "10.0.0.1" vouched for that address, while a
  certificate carrying the address as a proper iPAddress entry never
  matched. An IP address now matches only iPAddress entries, byte for
  byte (RFC 9525 6.3); a host name matches as before.

x509_parse_ip() is the new, exact parser behind that: dotted IPv4
without leading zeros, IPv6 with "::" compression and a trailing dotted
IPv4, brackets and a zone id allowed.

On the way: the built-in client kept the caller's pointer for the host
name, and net_http passes one that dies with the transfer while the
connection lives on in the pool - where a TLS 1.3 session ticket
arriving later is filed under that name. It keeps its own copy now.

BearSSL and mbedTLS get the bare address too; their own IP handling is
theirs (BearSSL's minimal verifier has none, so such a connection fails
verification, as it did).

Tests: the crypto suite parses valid and malformed literals and matches
a SAN with DNS, IPv4, IPv6 and a DNS-spelled address, plus an IP-reading
commonName. The local openssl matrix connects to 127.0.0.1 against a
leaf with IP:127.0.0.1 (refused before this change) and one with
DNS:127.0.0.1 (now refused).
2026-10-01 04:18:09 +02:00
libretroadmin b5658e5a7f crypto, net/tls: ChaCha20 first without AES instructions; X25519 on 32-bit limbs
Where AES runs in software - the 3DS, the Vita, older phones - AES-GCM
costs about four times what ChaCha20-Poly1305 does (116 against 32
instructions an octet on 32-bit ARM), yet the client offered it first,
and servers that follow the client's order chose it. The client now
offers ChaCha20-Poly1305 first when crypto_aes_hw() reports no AES
instructions; the TLS matrix checks a build without them negotiates it
on 1.2 and 1.3, through ssl_socket_retro_suite().

X25519 on machines without a 128-bit integer ran on the generic
Montgomery arithmetic, 6.1 million instructions an operation on 32-bit
ARM. It now has arithmetic of its own mod 2^255 - 19 in ten 32-bit
limbs of 26 and 25 bits, every product one 32x32->64 multiply: 1.9
million on the Vita's ARMv7, 2.1 million on the 3DS's ARMv6.
2026-10-01 04:01:54 +02:00
LibretroAdmin cec500d0cb task_http: resume an interrupted download to a file
A download to a file - task_push_http_download_file(), which the core
updater and playlist thumbnails use - that lost its connection part way
through failed outright and deleted what it had. On a flaky link a
large core or asset bundle could fail at 90% every time.

It now picks up where it stopped, inside the same task:

- On the first response, a 200 that advertises "Accept-Ranges: bytes"
  and a strong ETag (or else Last-Modified) makes the download
  resumable. A weak ETag cannot validate a byte range.
- When the transfer then fails in transport mid-body - not an HTTP
  error, not a full disk, not a cancel - the task opens a new request
  for the same URL with "Range: bytes=N-" and that validator in
  If-Range, N being what is already on disk, and keeps writing to the
  same file.
- A 206 must continue exactly at N (Content-Range is checked); a 200
  means the server sent the whole resource again - it ignored the range,
  or If-Range found it changed - and the file starts over; anything
  else fails the download and removes the file, as before.
- At most three resumes, each only after progress.

A completed resumed download reports status 200 to the callback, since
the whole file was delivered (the thumbnail callback checks for exactly
200), and progress covers the whole file rather than the last slice.

net_http gains net_http_header(), a lookup of a response header while
the transfer is still running, which is what the sink needs to decide.

Two task_http fixes on the way: a failed download to a file set its
error string twice and leaked the first, and a task cancelled before
its connection became a transfer leaked the connection.

New samples/tasks/http/http_resume_test drives the real task_http.c,
net_http.c and task queue against a scripted server: resumed once,
resumed twice, resource changed (the new body alone, not spliced),
wrong Content-Range, no validator (no resume tried), and a connection
that drops every time (fails after three resumes). Six of its checks
fail on the previous code, where every one of these downloads failed.
2026-10-01 04:01:36 +02:00
LibretroAdmin beef1f13b9 net_http: share the header-text compactor; test the real one
The Emscripten backend gets response headers from the browser as one
text blob and turns it into net_http's header block itself. Its test
could not build task_http_emscripten.c (emcc only), so it carried a
copy of the parser - and the copy had already drifted: it still split
into a string_list, while the backend had moved to the block.

The conversion is now net_http_headers_compact() in net_http.c: header
text in, the block net_http_headers_take() returns out, in place, with
CR and trailing spaces trimmed, blank lines dropped and the final line
kept without its newline. The Emscripten backend calls it on the buffer
the browser filled; http_emscripten_headers_test links net_http.c and
calls it too, so the response side has no copy left to drift. The
request side stays a copy, being static to the emcc-only file, as the
test's header says.

http_emscripten_headers_test: the same response cases (splitting,
blank and final lines, lower-cased names, NULL and empty input) against
the real function, under ASan and UBSan.
2026-10-01 03:51:10 +02:00
LibretroAdmin 9aafb30bfb net_http: keep the body when more than Content-Length arrives
A response that ran past its Content-Length failed the transfer:
status -1, no body, although the whole body had arrived and was sitting
in the buffer. It can happen whenever the excess lands in the same read
as the body - with the header block, or with a sink, whose receive
window is not trimmed to what is left of the body.

The body is now complete at Content-Length. What follows it is garbage
or a response nobody asked for, so it is dropped together with the
connection - the same treatment as bytes after a response with no body.

net_http_semantics: "Content-Length: 2" followed by "okEXTRA-BYTES"
delivers "ok", with and without a sink, and the next request goes out
on a new connection. Both delivery checks fail on the previous code.
2026-10-01 03:51:06 +02:00
LibretroAdmin 65f42ddb41 net_http: drop a followed redirect's body as it arrives
A 3xx whose Location is going to be followed already kept its body
out of the caller's sink, but without a sink it was still read in
whole: the receive buffer grew to the advertised Content-Length - up to
the 256 MiB ceiling, on the server's say-so - only for net_http_redirect()
to throw it away.

Such a body is now handled the way a sink body is - drained after every
read, never sized to Content-Length - except that it goes nowhere. One
predicate, net_http_body_streams(), replaces the sink tests on the
body path. The redirect body is still read through, so the connection
stays usable for the redirected request.

net_http_semantics: a 302 with an 8 MiB body is followed with no
allocation reaching 1 MiB (it reached 8 MiB before), on the same
socket.
2026-10-01 03:51:03 +02:00
LibretroAdmin 347baf9fb9 net_http: send the request a slice per update instead of blocking
net_http_send_request() wrote the request with blocking sends - a
dozen for the head, then the whole body - before returning. net_http's
sockets are non-blocking, so once the body outgrew the socket buffer,
socket_send_all_blocking() spun on EAGAIN until the server had taken
all but the last buffer's worth. Measured against a server reading at
2 MB/s, a 16 MiB PUT spent 6057 ms in one net_http_update() call, at
100% CPU. In an unthreaded build that call is on the video thread; in a
threaded one it holds up every task queued behind it. Cloud sync
uploads of saves and states are exactly this request.

The request now goes out like the response comes in:

- The head is built once into one buffer, byte for byte what the old
  send sequence wrote: one send where there were a dozen.
- Each net_http_update() sends as much as the socket takes, at most
  NET_HTTP_SEND_BUDGET (256 KiB, the drain budget's twin) per call,
  then returns. Postdata goes straight from the caller's buffer; a
  streamed body is pulled a 64 KiB run at a time into one buffer kept
  for the transfer.
- A full socket marks the transfer blocked, so net_http_wait() waits
  for it to become writable - no spinning, no sleeping; stopping on the
  budget does not. Over TLS the last bytes are flushed out of the TLS
  layer before the request counts as sent.
- The stale-pool replay and a redirect start the send over, after the
  source rewind they already did.

Same 16 MiB upload: longest call 6057 ms -> under 1 ms, total time
unchanged (8.07 s), and CPU for the whole transfer 6.0 s -> 0.01 s with
a caller that waits between updates. Requests that fit in the socket
buffer go out in the first call as before.

cheevos_login_test: a 12 MiB PUT through net_http to a server that
stops reading for a second, plain and over TLS, must never spend
250 ms in one update; the longest is 4-8 ms. Plain and BearSSL spent
1020 and 1033 ms on the previous code. The request bytes the login
checks validate one for one are unchanged.
2026-10-01 03:31:27 +02:00
LibretroAdmin 1e44eb6774 net/ssl: make non-blocking send work on all three TLS backends
ssl_socket_send_all_nonblocking() had no callers, and none of the
three implementations was usable:

- mbedTLS took MBEDTLS_ERR_SSL_WANT_WRITE - an ordinary full socket -
  for an error, and on success reported the whole length as written
  whatever mbedtls_ssl_write() had actually taken.
- The built-in client sent the whole buffer blocking.
- BearSSL was close, but copied from a NULL buffer when the engine was
  full and could not say whether bytes it had taken were still waiting.

The contract is now written down in net_socket_ssl.h: take as much as
fits without blocking and return that (0 when the socket is full, -1
on error); after a 0, call again with the same buffer, at least as
long; once the last byte is taken, call the new
ssl_socket_flush_nonblocking() until it reports 1.

- mbedTLS remembers the length of a write that returned WANT_WRITE and
  repeats it, as mbedtls requires; a positive return already means the
  records are on the wire, so its flush has nothing to do.
- The built-in client seals one record from the bytes it takes and
  keeps whatever the socket did not accept in a per-connection buffer,
  sending it before sealing the next. A blocking send drains it first
  so records leave in sequence; close_notify is skipped behind a
  half-sent record rather than wait on a peer that stopped reading.
- BearSSL pushes its pending records before taking more, refuses a
  closed engine, and flushes in a loop while the socket keeps taking.

samples/network/cheevos_login gains a built-in TLS lane (SSL_BACKEND=
retro, trusting the test CA through ssl_socket_retro_set_trust_pem) and
a check that drives each backend's non-blocking send directly: a
12 MiB PUT to a mock /upload that stops reading for a second before
taking the body. Every call must return at once and the server must
receive every byte. mbedTLS fails it on the previous code.
2026-10-01 03:31:23 +02:00
libretroadmin 156de513c9 crypto: AArch64 AES-GCM eight blocks at a time; ChaCha20 on NEON
The AArch64 AES-GCM path still did as x86 did before it changed: CTR
four blocks at a time, round keys reloaded and byte-swapped every round,
each counter block rebuilt through a scalar swap, and GHASH reduced
after every block. CTR now keeps eight blocks in flight with the keys
loaded once, the counter stepped by one add in a lane-reversed vector,
and GHASH folds eight blocks per reduction with Karatsuba multiplies:
AES-128-GCM takes 40% fewer instructions, 3.4 per byte down from 5.7.

ChaCha20 ran scalar on AArch64, where it is what TLS chooses on cores
without the AES instructions. It now runs four blocks at once on NEON,
which every AArch64 core has: ChaCha20-Poly1305 takes 60% fewer
instructions, 9.6 per byte down from 24. Counts are executed AArch64
instructions under QEMU, start-up subtracted.
2026-10-01 03:30:32 +02:00
libretroadmin 118bb10b9a samples/tls_retro: link retro_eventcount into tls_http
net_http now waits on an eventcount, so tls_http, which links net_http
over the built-in TLS client, links rthreads/retro_eventcount.c as the
HTTP task samples do; without it the tool failed to link and the TLS
matrix stopped there.
2026-10-01 03:30:28 +02:00
LibretroAdmin 2302213409 net_http: parse bracketed IPv6 literals in URLs and redirects
An IPv6 address in a URL is bracketed because it contains colons
(RFC 3986 3.2.2): http://[::1]:8080/x. Both places that split host
from port took the first colon:

- net_http_connection_iterate()/_done() stopped the host at "[" and
  refused the rest as a bad port, so such a URL could not be requested
  at all;
- net_http_redirect() split the joined URL the same way, so a redirect
  to a literal failed (or, with digits in the right place, went to a
  junk host and port).

Both now treat a leading "[" as a literal running to its "]", with the
port colon only after it; an unterminated, empty or trailing-garbage
literal is refused. The domain keeps its brackets, which the Host
header needs ("Host: [::1]:8080"), and the resolver strips them before
getaddrinfo(), which wants the bare address. url_join needed nothing:
it never looks inside the authority.

Not covered: TLS to an IPv6 literal still passes the bracketed form
as the server name, as it passes any other host.

http_limits_test (net_http.c included whole, resolver stubbed): URL
split with and without a port and with a zone id, the three malformed
forms, the resolver's argument, and a redirect to http://[::1]:8443/v6.
All fail on the previous code.
2026-10-01 03:00:47 +02:00
LibretroAdmin 0324ba4909 net_http: a 1xx block's framing headers do not carry into the response
An interim response (100, 102, 103) is followed by the real status line
on the same connection, and its headers were cleared before reading it
- but not what they had set. bodytype, len and content_len survived:

- "Transfer-Encoding: chunked" on a 100 stayed T_CHUNK, and since
  Transfer-Encoding wins over Content-Length, the final response's
  "Content-Length: 2" was ignored and its body parsed as chunks.
- "Content-Length: 5" on a 103 stayed T_LEN 5, so a close-delimited
  final body was cut after five bytes and the connection pooled with
  the rest unread.

Interim blocks now reset the framing along with the headers. Costs
three stores per 1xx block; nothing on the body path.

Two new net_http_semantics checks, both failing before.
2026-10-01 03:00:43 +02:00
libretroadmin 7aa27bc5fe net/tls: records land in the caller's buffer; the end of the stream ends a download
A received record was decrypted in place, copied to a plaintext buffer,
and copied again into the caller's. The plaintext now stays where it was
decrypted, and when the caller's buffer holds the whole record - every
net_http read, which asks for 32 KiB or more - it is decrypted straight
into it: a download takes 31% fewer instructions. The AES-GCM key
schedule is built once per key rather than once per record.

The end of the stream came back as 0 bytes, which net_http takes for
'nothing yet': a body that runs until the server closes the connection
never finished, every byte received and the transfer polling forever.
It is now -1 with the error flag, as plain sockets and the library
backends report it. tls_http drives net_http over the built-in client in
the TLS matrix, four version and cipher pairs, checking the body by hash
and failing a transfer that never completes.
2026-10-01 02:57:20 +02:00
LibretroAdmin 3ef1a02a5d webdav: bound every scan in the Digest challenge parser
The WWW-Authenticate challenge is whatever the server sends with a
401, and the parser trusted its shape:

- realm, nonce and opaque took strchr(ptr, '"') + 1 - ptr as a length.
  With no closing quote strchr() returns NULL, the length is a huge
  value, and malloc() of it fails or the strlcpy() that follows writes
  through NULL.
- An unknown unquoted parameter was skipped with
  while (*ptr != ',' && *ptr != ','), which never checks for the end
  of the string, so one at the end of the line ("stale=FALSE") read on
  past it until it happened to meet a comma.
- qop and unknown quoted values had the same missing-quote hole.

Every value is now found with a bounded scan, and a challenge that
cannot be parsed is refused - which the caller already handles as
"cannot re-authenticate". Quoted values go through one helper that
copies them out and replaces any earlier copy, so a repeated parameter
no longer leaks the first; the HA1 hash is recomputed rather than
leaked when realm repeats. qop is read as a list whose members may be
spaced ("auth-int, auth" was refused), and a quoted algorithm is
accepted.

webdav_reauth_test parses well-formed and malformed challenges from
exactly-sized buffers under ASan. The previous parser aborts on the
first malformed one.
2026-10-01 02:56:44 +02:00
LibretroAdmin 1924d2a5dc webdav: take the Digest challenge as const
webdav_needs_reauth() now walks the response's header block with
net_http_header_next(), which hands out const char *, and passed each
line to webdav_create_digest_auth(char *) - a -Wdiscarded-qualifiers
warning. The parser only ever reads the challenge (every field it
keeps is copied out with strlcpy/strdup), so its parameter and cursors
are const now.
2026-10-01 02:48:25 +02:00
libretroadmin 963e80d925 menu: rebuild only the menu context after Update Assets
Update Assets rebuilds the menu's own context once the archive is
extracted - context_destroy then context_reset against the running
video driver, the same pair the menu goes through when it survives a
video driver swap - instead of CMD_EVENT_REINIT. The menu reads its
icons and fonts from the assets directory when that context is built,
so the new assets show at once without tearing down and recreating
the video driver, its context and its swapchain.

Texture and font releases go to the video thread under the threaded
wrapper, as on the driver-swap path. The notification widgets keep the
assets they loaded until the video driver is next initialised.
2026-10-01 02:46:56 +02:00
LibretroAdmin b071b96a07 net_http: fall back to the next address family when socket() refuses one
getaddrinfo() with AF_UNSPEC lists IPv6 addresses next to IPv4 ones,
often first. On a system without IPv6 - a kernel or console network
stack built without it - socket() refuses AF_INET6, and that ended the
attempt in two places:

- net_http_new_socket() created the socket for the first address only,
  so an IPv6 address listed first failed the transfer outright with
  "socket_create_failed", IPv4 addresses untried;
- the connect loop moved on with socket_next(), which returns -1 when
  socket() fails for the next address, and the loop read that as the
  end of the list, so an IPv6 address mid-list hid every address after
  it.

Both now go through net_http_socket_for(), which creates the socket for
the first address at or after the current one that the system accepts,
and the connection remembers that address's family so connect starts
there. An extra socket() call happens only on a refused family; the
common case is unchanged. Full Happy Eyeballs (racing connects) is not
attempted.

http_limits_test, with socket() stubbed to refuse AF_INET6: an IPv6
address first connects over the IPv4 one after it, and with the first
IPv4 address refusing the connection and an IPv6 one next, the IPv4
address after that is still tried. Both fail on the previous code.
2026-10-01 02:45:24 +02:00
LibretroAdmin 566f3ae9a0 net_http: cap idle pooled connections and close them after 30 s idle
Every keep-alive connection a finished transfer handed back stayed in
the pool until the server closed it and select() noticed. Nothing
bounded how many - each one a descriptor here and a slot on the
server - and nothing aged them. A connection whose NAT mapping or
server keep-alive timer expired silently is never reported readable,
so it was reused: the request went into a dead socket and the transfer
waited out its timeout.

A finished transfer now hands its connection back through
net_http_conn_pool_release(), which stamps it idle and, past 8 idle
connections, closes the one idle longest. The pool sweep that already
runs when a transfer looks for a connection closes any idle for more
than 30 s before it even asks select(). Reconnecting costs a handshake;
a dead socket cost the whole timeout.

Neither touches the receive path: the release runs once per finished
transfer and the reap once per pool lookup. With the pool owned by the
driving thread, neither takes a lock.

http_limits_test: 20 finished connections leave 8 idle (all 20 stayed
before), the longest-idle go first, and a connection is reused at 15 s
idle and closed at 30 s.
2026-10-01 02:45:20 +02:00
LibretroAdmin 02dd573a34 net_http: cap the DNS cache at 32 entries
Every distinct host:port added a cache entry, and a successful one had
its timestamp bumped on every hit, so it lived for as long as it kept
being used. A session that touches many hosts - thumbnail mirrors,
cloud sync, netplay lobbies, core and asset downloads - grew a list
that is walked on every connect, with no bound.

Inserting a new host:port now evicts the least recently used entry
once there are more than 32. A lookup still in flight is never the
victim: its resolver owns the entry until it publishes. The cap costs
one walk on insert only; a cache hit and the receive path are
untouched. The expiry sweep also reads the clock once per walk rather
than twice per entry.

New samples/tasks/http/http_limits_test (net_http.c included whole,
clock and resolver stubbed), threaded and unthreaded, under ASan+UBSan:
100 distinct hosts leave 101 entries before this change and 32 after,
the newest kept and their addresses freed as they go.
2026-10-01 02:45:17 +02:00
LibretroAdmin e2e07068eb net_http: drop the DNS cache and pool locks
net_http took a mutex around every DNS cache and connection pool
access and parked DNS waits on a condition variable, on the premise
that any thread might drive a transfer. None does: in RetroArch every
net_http_new/update/wait/delete comes from task_http on the task
queue's worker, or on the main thread with threaded tasks off, and
switching between the two stops the queue first, so the handover is
ordered by the join. The only real concurrency is the DNS resolver
threads, each of which fills in one cache entry.

So the cache and pool now belong to the driving thread, and nothing
takes a lock:

- A resolver reads its entry's domain and port in place (they are set
  before the thread starts and never change), writes addr, publishes
  valid with a release store and touches the entry no more. The owner
  reads valid with an acquire load, and reads addr, frees the entry or
  joins its thread only after that. The strdup the resolver took under
  the lock, a copy of something nobody could change, is gone.
- The pool loses its lock outright; only the driving thread uses it.
- connect no longer pins the cache entry with a user count: the pin
  covered another thread's expiry sweep running mid-connect, which
  cannot happen.
- net_http_wait() parks on a retro_eventcount the resolver notifies
  after publishing, re-checking the lookup after registering so a
  publish in between is never missed, and bounded by the caller's
  timeout as before. net_http_init() creates it; there are no locks
  left to create.
- net_http_deinit() can no longer deadlock: resolvers wait on nothing,
  so joining one only lasts as long as its getaddrinfo().

The sweep still never joins a resolver that has not published - that
used to deadlock on the lock and would now stall the driving thread for
the whole lookup - and find() no longer reads addr before valid is
published, which TSan would report as a race now that no lock orders
the two.

The rule is documented next to net_http_init() in net_http.h. The
stress test's concurrency case drove six transfers from six threads,
which RetroArch never does; it now interleaves them on one thread as
the task queue does, with the resolvers running alongside under TSan.
The DNS lifetime test loses its nested-connect case (the cross-thread
sweep it modelled) and the init-failure test its resolver strdup case
(the strdup is gone). The tests that build net_http with HAVE_THREADS
link retro_eventcount.c, as Makefile.common and griffin already do
under HAVE_THREADS. The samples network job now fails if net_http.c
calls an slock_ or scond_ function.

Stress, transport-failure, task-lifetime, DNS-evict and the semantics
harness pass under TSan and ASan+UBSan.
2026-10-01 02:45:14 +02:00
LibretroAdmin dba5458fb5 net_http: keep response headers in one block instead of a string_list
Every transfer allocated a string_list for its response headers: the
struct, a 32-slot array, and a strdup per header line, then freed all
of it when the task finished - on the task thread, or on the video
thread in an unthreaded build. A typical response has 10 to 20 lines;
a 64-line one cost 67 allocations before a byte of body arrived.

The headers now go into one block as they are parsed: each line
NUL-terminated, an empty line at the end, grown by doubling from 1 KiB
and bounded by the existing 64 KiB header cap. It is allocated with
the first header line (a failed connect allocates nothing) and handed
over whole. In the harness, 64 header lines cost one allocation more
than one line does. The Location offset replaces the pointer into the
list, since the block may move as it grows.

API:
  net_http_headers_take()  the block, ownership to the caller (free());
                           same NULL rules as net_http_headers_ex()
  net_http_header_next()   walk the lines
  net_http_header_value()  first field by name, case-insensitive
  net_http_body_is_framed() takes the block

net_http_headers() / net_http_headers_ex() remain for code outside
RetroArch, building a string_list from the block on demand; nothing
in-tree calls them.

http_transfer_data_t.headers is the block. task_http hands it over
and frees it; task_core_updater frees it; the emscripten backend
compacts the browser's header text into the same layout in the buffer
it already had, so it also drops to one allocation. WebDAV, S3 and
Google Drive walk it with net_http_header_next(), and S3's ETag lookup
uses net_http_header_value(). The WebDAV, S3, transport-failure and
cheevos harnesses build or take blocks; the S3 harness links net_http.c
for the helpers.
2026-10-01 02:28:04 +02:00
libretroadmin 2a0757e911 menu: measure navigation auto-repeat between input polls
The menu acts on input collected before the frame polls, so the state
menu_event() reads is one poll old. The runloop stamps each menu poll
with the frame's time and hands menu_event() the stamp of the poll whose
state it is reading (input_time_us); auto-repeat measures the hold on
that clock. Time spent after a poll - Ozone and XMB build a whole
playlist synchronously when the selection lands on it - is not counted
as time the button was held, so one press moves the selection once,
while a button held across the stall repeats as soon as a later poll
sees it down. Steady-state repeat timing is unchanged.

samples/menu/nav_repeat_stall drives the real runloop with a scripted
input driver whose poll stalls, and runs in the Linux menu harness
workflow.
2026-10-01 01:58:22 +02:00
LibretroAdmin 92da172f10 net_http: resolve redirects with net_http_url_join
net_http_redirect() had three ways to read a Location. An absolute URL
allocated a whole connection object just to parse it; an absolute path
replaced the path; anything else went through
fill_pathname_resolve_relative, a filesystem helper that knows nothing
of URLs. "../up" was not resolved, "?q=1" was treated as a file name
and replaced the last path segment, a "//host/path" reference stayed
on the old host, and a fragment went out in the request line.

It now rebuilds the current URL, joins the Location onto it and parses
the result: one path for every form, and the place to add IPv6
literals or userinfo if a caller ever needs them. The fragment is
dropped, the port is validated, and the Location is not decoded -
"%20" must stay encoded in the request line. One scratch allocation
replaces the connection object and its copies on absolute redirects;
file_path.h is no longer needed.

Runs on 3xx only. 5 new checks, 4 failing before (the %20 one guards
against decoding).
2026-10-01 01:56:59 +02:00
LibretroAdmin 51cf924533 net_http: add net_http_url_join and net_http_urldecode
Master could encode a URL but not decode one, and had no way to
resolve a reference against a base URL: redirects did it by hand in
three branches, one of them borrowing fill_pathname_resolve_relative,
a filesystem helper. The ideas come from the 2018 index_parse branch;
the code does not.

net_http_url_join() resolves per RFC 3986 section 5 into caller
storage with no allocation: empty reference, absolute http(s) URL,
"//" network path, "/" absolute path, "?" and "#" references, and
relative paths under the base directory, followed by "." / ".."
removal in place. index_parse strdup'd the base to punch NULs into
it; this walks pointers. A reference with any other scheme, or a
buffer too small, returns -1 with an empty result.

net_http_urldecode() and net_http_urldecode_inplace() decode %XX,
reading both digits before writing so the in-place form is safe
(the 2018 version rewrote the source nibbles). A bad escape is copied
as is, and '+' is not a space outside form bodies. Documented as not
for URLs about to be requested: decoding %20 there would put a space
in the request line.

Neither runs on the body path. The unit matrix is in the
net_http_semantics harness.
2026-10-01 01:56:56 +02:00
LibretroAdmin 7e80941861 net_http: create locks only in net_http_init, strncmp the scheme
net_http_new_socket() still created the DNS cache and pool locks on
first use, the unsynchronised initialisation net_http_init() exists to
replace: two threads arriving together could each create one and then
lock different objects. RetroArch calls net_http_init() before the
task queue starts, as do the threaded test harnesses, so the lazy path
only ever served programs that skipped it - and served them racily.
It is gone; such a program now runs unlocked (slock_lock(NULL) is a
no-op), which is documented in net_http.h and next to the sample,
which now calls net_http_init()/net_http_deinit(). No lock is added:
this removes three creations from the connect path.

net_http_connection_new() compared the scheme with memcmp, reading 7
or 8 bytes of a URL that may be shorter. strncmp stops at the NUL.

http_transfer_stress_test's silent-peer case called net_http_deinit()
to drop pooled connections and carried on relying on the lazy
creation; it now calls net_http_init() after it, as its re-init case
already did.

One new check (an exactly-sized "ht" URL), which ASan reports as a
heap-buffer-overflow on the previous code.
2026-10-01 01:56:52 +02:00
LibretroAdmin dbc82bd17f net_http: bound headers and close-delimited bodies, reject bad chunk sizes
Content-Length was already capped at 256 MiB, but nothing else was.
A header block with no end, a single header line with no newline, or
an endless run of 1xx blocks doubled the buffer for as long as the
peer kept sending; so did a close-delimited or chunked body. Headers
now fail past 64 KiB in total, interim blocks included, and the
receive buffer stops at the Content-Length ceiling plus one window
for every framing.

A chunk-size line with no hex digits parsed as 0, the last chunk: the
transfer "succeeded" with what had been decoded so far and the
connection went back to the pool with the rest unread. It is an error
now, and the connection is dropped.

The stale-pool replay only looked at the receive position, which is
back at 0 once a complete status line has been consumed. A pooled
connection that died right after it was replayed, appending a second
header set to the first - against the function's own promise that
nothing is replayed once response bytes have been seen.

A buffered Content-Length body no longer takes the drain loop's
window-floor growth. Its buffer is exactly the body, so near the end
the loop doubled it - 16 MiB to 32 MiB for the final 32 KiB - and
P_DONE shrank it back: two reallocs, and two whole-body copies on
allocators without mremap, per download. This removes work from the
drain loop; nothing is added to it.

6 new checks, all failing before; the buffer-size check reads the
largest realloc through --wrap=realloc.
2026-10-01 01:56:48 +02:00
LibretroAdmin 9826950e56 net_http: cap redirects, rewind streamed bodies, fix "GET //foo"
request.path is kept without its leading slash, since the request
line writes " /" before it, but an absolute-path redirect stored
"Location: /foo" whole and went out as "GET //foo". The slash is
dropped now.

Redirects were followed with no limit, so two URLs pointing at each
other kept a transfer going forever. After 8 the transfer fails
("redirect_limit").

A streamed request body was not rewound before the redirected send.
The source was already drained, so the resend on the pooled socket
went out as headers with a short body, and only the stale-pool replay
happened to rescue it on a second connection. It is now rewound first,
the same contract as that replay, and fails cleanly when the source
cannot rewind. The 3xx's own body is no longer handed to the caller's
sink - a download to a file used to start with the redirect page -
and the sink bookkeeping starts over for the new URL. A failed
net_http_connection_new() on an absolute Location is checked.

POST is still not rewritten to GET on 301/302/303: WebDAV PUT and
MKCOL would change meaning silently.

Costs one counter and a flag on 3xx only. 8 new checks, 5 of them
failing before (the others guard behaviour that already held).
2026-10-01 01:56:44 +02:00
LibretroAdmin 4eabee2419 net_http: read Connection and Transfer-Encoding as lists, drop P_DONE walks
Both headers are comma-separated lists, and both were matched as one
exact line. "Connection: Keep-Alive, Close" or "Connection:close"
did not close, so the socket went back to the pool; "Transfer-Encoding:
gzip, chunked" was not chunked, so the body was read to a close that a
keep-alive server never sends. Both are now token scans on those two
lines only.

HTTP/1.0 closes unless the server says keep-alive; the version is
taken from the status line, and such a connection no longer goes to
the pool to cost a failed send and a replay on the next request. A
close-delimited body ended the connection too, and it is no longer
pooled either. A Content-Length arriving after "chunked" reframed
the body; Transfer-Encoding now wins whichever comes first.

Location is captured during the same walk, as a pointer into the
string_list copy of the line (not the receive buffer, which is
memmove'd afterwards), and it now accepts "Location:" without the
space. P_DONE used to walk the whole header list twice, once for
Connection and once for Location; it reads flags now.
net_http_body_is_framed() takes the same list test.

GET 200 pays one extra prefix compare, on the Connection and
Transfer-Encoding lines only. 7 new checks, all failing before.
2026-10-01 01:56:40 +02:00
LibretroAdmin d9671eda2f net_http: finish HEAD, 204, 304 and 1xx responses at the headers
A response with no body was read as if it had one. A HEAD answered
with Content-Length, or a 304 carrying the cached copy's length,
waited for that many bytes; a 204 without a length was taken as
close-delimited and waited for the close. On a keep-alive server
none of that ever arrives, so every one of them sat until the caller
timed out - and had it arrived, it would have been the next
response on that socket.

Such a response now ends at the blank line: HEAD, 204 and 304 by
definition, 101 too, with the connection marked unusable. A 1xx
other than 101 is an interim block and the real status line follows
on the same connection; only 100 was handled that way, so 102 and
103 were reported as the final status with the rest of the stream as
their body. Interim headers are dropped rather than reported as the
final response's, and redirects reuse that clear instead of freeing
and reallocating the header list. Bytes left over after a bodyless
response keep the socket out of the pool, since the next request
would read them first.

GET 200 does not take this path: one status test at the blank line.

New samples/network/net_http_semantics harness: the real net_http.c
against a scripted in-process server that logs which connection each
request arrived on, run under ASan+UBSan and TSan in the samples
network job. 11 of its 18 checks fail on the previous code.
2026-10-01 01:56:36 +02:00
libretroadmin 85d24f5606 smb: SMB 3.1.1 signs with AES-GMAC where the server offers it
Signed SMB 3 traffic, every read reply included, carried an AES-CMAC,
which chains each block on the one before and so runs a block at a
time: a 256 MiB read from Samba with signing mandatory came at 380 MB/s.
NEGOTIATE on 3.1.1 now offers SIGNING_CAPABILITIES, GMAC before CMAC,
and where the server takes GMAC the signature is a GCM tag over the
message with the MessageId, sender and CANCEL bits as nonce: the same
read comes at 1.3 GB/s, and a server's own signing is as much cheaper.
Servers without GMAC keep CMAC.

The Samba matrix checks which algorithm was chosen, GMAC by Samba's
default and CMAC from a server that allows no other, through
rsmb_get_sign_alg().
2026-10-01 01:46:30 +02:00
libretroadmin fd54097b79 crypto: X25519 and P-256 ahead of BearSSL and mbedTLS
The handshake's public-key work trailed: X25519 took 0.18 ms where
BearSSL takes 0.13, P-256 key generation 0.43 where both take 0.31, and
an ECDSA check 0.90 where BearSSL takes 0.83. X25519 ran on the generic
Montgomery arithmetic, with an inversion that multiplied for every set
bit of p - 2; on 64-bit machines it now has arithmetic of its own mod
2^255 - 19 in five 51-bit limbs and the inversion by the usual addition
chain. The curve multiply went a bit at a time, an addition per bit; it
now goes four bits at a time from a table on the heap, chosen by
scanning every entry, a zero digit adding anyway and dropping the sum,
and a signature check does both of its products in one pass. X25519
0.045 ms (BearSSL 0.13, mbedTLS 0.67); P-256 key generation 0.26 (0.32,
0.33), ECDH 0.27 (0.53, 0.84), ECDSA verify 0.35 (0.94, 1.43). RSA-2048
verification ties mbedTLS with a fresh key, as a handshake has one.
2026-10-01 01:18:58 +02:00
libretroadmin c252393c13 crypto: AES-GCM and ChaCha20-Poly1305 ahead of BearSSL and mbedTLS
On x86 with AES-NI, AES-GCM ran at 1.3 GB/s against BearSSL's 3.1, and
ChaCha20-Poly1305 at 0.39 against 0.46. CTR now keeps eight blocks in
flight, loads its round keys once per call, and steps its counter with
one vector add on the byte-reversed block, where the scalar rebuild of
every counter had been the cost; GHASH folds eight blocks per reduction
with Karatsuba multiplies. ChaCha20 runs four blocks at once on SSSE3,
chosen at run time, and Poly1305 uses 44-bit limbs where the compiler
has a 128-bit integer. Per 16 KiB record: AES-128-GCM 4.3 GB/s (BearSSL
3.0, mbedTLS 0.23), AES-256-GCM 3.3 (2.8, 0.22), ChaCha20-Poly1305 0.68
(0.47, 0.29), SHA-256 1.2 (0.27, 0.28). Other machines keep their paths.

test_bulk_paths checks the bulk paths against the block functions over
lengths around each group size and counters across the 32-bit wrap.
2026-10-01 00:49:42 +02:00
LibretroAdmin 4b9dfb2114 build: the tree's own headers are found before the system's
The compile line searches -I., libretro-common/include, deps and the
other in-tree dirs first, then pkg-config's paths, then the absolute
dirs configure found on the system (/usr/local/include on the BSDs),
so a same-named header installed on the system cannot stand in for one
of ours. tools/include_order_check.sh plants colliding headers in both
kinds of system path and compiles retroarch.c against them.
2026-10-01 00:19:20 +02:00
LibretroAdmin 79019b2933 qb: the moc check writes to config.log
A moc that is found but does not work says why in config.log, like
every other configure check, and the qmake query stays off the
configure output.
2026-10-01 00:19:16 +02:00
libretroadmin aad8374288 msvc 2005-2017: TLS, keychain, SMB and NFS; the CA bundle in parts
The Visual Studio 2005 to 2017 projects build the network stack as the
2019 and 2022 ones do: the TLS client, the keychain, and the SMB and NFS
clients. What kept them out was the CA bundle, one 255 KB string, where
MSVC before 2019 stops at 65535 bytes (fatal error C1091). net/cacert.h
now holds it in five arrays, each under 60000 bytes and ending where a
certificate does, and x509_trust_load_pem_parts() loads them all into
one trust store with no copy. tools/cacert_split.py writes the header,
from a new cacert.pem or from itself, byte for byte the same bundle.

The rest already kept to what those compilers take: C89, the LL/ULL
suffixes code they build uses, and Windows entry points resolved at run
time. The compile matrix builds the stack for 32-bit Windows in C89 at
the 0x0410 and 0x0501 floors, and checks no part of the bundle reaches
the limit.
2026-09-30 23:42:30 +02:00
LibretroAdmin dbe9cdee76 wayland/vulkan: include the backport header by its name
The Vulkan context builds against the older libwayland fallbacks like
the GL one.
2026-09-30 23:19:09 +02:00
LibretroAdmin 31bbf0c34b wayland: the GL window follows configures after the splash, at any scale
Both context drivers share one resize step: the compositor's
configures count from the first resize whatever the scaling, and the
surface takes the buffer scale where it can. With fractional scaling
or an older compositor the GL window follows the compositor's size
once the splash is gone, and on whole scales it is redrawn at the
scale it is on.
2026-09-30 23:19:06 +02:00
libretroadmin 8412e7be79 android: save before onPause()/onStop() return, not after
A task swiped away from Recents is killed as soon as it drops to the
background, which is right after onPause() returns, and a stopped
process can be reclaimed once onStop() has returned; neither case
delivers onDestroy(). APP_CMD_PAUSE and APP_CMD_STOP are therefore
acknowledged only once the one-time background flush of SRAM, core
options and retroarch.cfg has run at the top of the next runloop
iteration, so the lifecycle callback waiting for the acknowledgement
cannot return with the settings unsaved. STOP performs the flush
itself when a timed-out PAUSE left it owed, a start or resume
supersedes a held acknowledgement, and the startup pump acknowledges
at once since nothing is loaded yet.

samples/input/android_lifecycle_flush reproduces the handshake with
a real UI thread and app thread, with sabotage lanes for
acknowledging before the save and for holding the acknowledgement in
the pump, and runs under ASan/UBSan and TSan in the samples/input CI
workflow.
2026-09-30 23:03:09 +02:00
LibretroAdmin a2fbc90900 Android: enable threaded video by default
DEFAULT_VIDEO_THREADED is now true for ANDROID as well as the Switch,
so a fresh Android install runs the core on one thread and presents on
another without the user having to turn it on.

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

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

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

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

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

  +uint	smb_client_readahead	1	0

in all three profiles. Regenerated with

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

No code change.
2026-09-30 22:52:54 +02:00
libretroadmin 0ddc58c44d 3ds, vita, switch: the built-in SMB client
The 3DS, Vita and Switch build smb://: the SMB and Kerberos clients
reach the network only through libretro-common's socket helpers, and
the crypto they rest on draws on each console's hardware random number
generator. The Wii U, whose crypto has only a clock to stir, is left
out: SMB derives its signing and sealing keys from that randomness. The
3DS keeps its SMB transfers at 64 KiB, as it does NFS's.

The compile matrix builds the SMB client, the Kerberos client and the
SMB VFS backend for each of the three under their Makefiles' defines.
2026-09-30 22:44:16 +02:00
LibretroAdmin 3298947b8a video_thread: the paced frame goes out on the vblank nearest its due time
The hold picks the vblank nearest the frame's due time on a grid from
the display, and aims at the due time itself when the grid is only
the video thread's clock (vsync off, no present timestamps).  A 120 fps
core on a 120 Hz display holds 120.

The threaded-video harness gains a pacing-rate lane: every shipped
pairing of content and display rate, with and without timestamps,
with a core that takes 1.3 ms and a render that varies, must hold
within 2% of its rate.
2026-09-30 22:37:57 +02:00
libretroadmin 66939756ee net/nfs: NFSv4.2 reads with READ_PLUS
Over 4.2 the client reads with READ_PLUS, whose reply sends the holes
of a sparse file as their extent rather than as zeros: a 32 MiB image
holding 1 MiB of data costs 1.1 MB on the network instead of 33.6 MB.
The runs are written straight into the reader's buffer, data copied
and holes zeroed. A reply no READ could have given - no progress short
of the end, an end before the size the file was opened with, runs out
of order - is a server's broken READ_PLUS, not a failed read: the
connection goes back to READ and the range is asked again as that.
nfs-ganesha 4.3 answers every range with one empty data run.

local_read_plus_test.sh reads a sparse file from fake_nfs42.py, a
minimal NFSv4.2 server that reports the holes the filesystem really
holds, and has Wireshark's own dissector decode the replies to check
their data and holes add up to the file. The Ganesha matrix checks the
fall back to READ; the network workflow installs tshark and runs both.
2026-09-30 22:35:56 +02:00
libretroadmin 41490906e3 remove TODO turn into actual task - now obsolete 2026-09-30 22:34:12 +02:00
LibretroAdmin 8f711ae2dd media: redraw 16x16 icons as pixel art
The 16x16 PNG and ICO are hand-pixeled from the 96x96 logo at native
resolution, with 1px outlines and hard edges so the invader stays crisp
at icon size.
2026-09-30 22:29:10 +02:00
LibretroAdmin 4a0b96a4a3 media: regenerate 16x16 icons from the 96x96 logo
retroarch-16x16.png and retroarch-16x16.ico are now downscaled from
retroarch-96x96.png so the small icons carry the current logo.
2026-09-30 22:26:29 +02:00
LibretroAdmin 343064c4a8 wayland: the cursor theme follows XCURSOR_THEME, XCURSOR_SIZE and the output scale
With no cursor-shape-v1 the context draws the cursor from a theme of
its own. It now loads the theme and size the toolkits use, at the next
whole scale up from the surface's (fractional scales included), with
the cursor surface's buffer scale set to match, and loads it again
only when that scale changes.
2026-09-30 22:17:20 +02:00
libretroadmin 8549dce77a net/nfs: after a server restart, opens are claimed back in its grace period
A server that restarts grants a client it knew its opens back during
its grace period and refuses new ones until grace ends - 90 seconds on
Linux. A file open for writing went through an ordinary open by path
after the restart, which a slow machine could not wait out, so its
next write failed. The client keeps its files that hold open state,
and whenever it identifies itself again it claims each back with
CLAIM_PREVIOUS - for 4.1 before RECLAIM_COMPLETE - where a server that
did not restart refuses harmlessly and the state it kept goes on. A
WRITE built before the call that re-dialled carries the old state, so
one whose file's state changed during it is sent again with the new.

The Ganesha restart rounds bring the server back with a 60 second
grace period, longer than any retry waits, so they test the claim.
2026-09-30 22:02:48 +02:00
LibretroAdmin fcfb331b2f content: a core that fails after the old session closed lands on the dummy core
A library that opens but is not a core (no retro_init, say) fails the
core stage after the old session is gone. The stage now runs again for
the dummy core: the drivers come back, the menu runs, and the failure
is said. On webOS a surface kept across the reinit that no init claims
is released.

Supersedes #19503.
2026-09-30 22:02:27 +02:00
libretroadmin c9c104c2aa 3ds, vita, switch, wiiu: the built-in NFS client
The 3DS, Vita, Switch and Wii U build nfs:// - the NFS client needs none
of the crypto the RAM-starved consoles leave out, and it reaches the
network only through libretro-common's socket helpers, which these
consoles already build. Where a console's socket layer may not sleep
in a select() with no descriptors, the grace-period wait takes the
platform's own sleep (RARCH_CONSOLE); the 3DS keeps its transfers at
64 KiB, where four connections at the 1 MiB other platforms grow to
would hold 8 MiB of buffers for Wi-Fi that gains nothing from them.

The compile matrix builds the client, its VFS backend and the
prefetcher for each of the four under the defines their Makefiles
set: Wii U against its own wiiu/include, Vita against new stubs of
vitasdk's socket headers.
2026-09-30 21:37:19 +02:00
libretroadmin c8daa642db vfs/smb: open files survive a server restart
An smb:// file open while its server restarted - a NAS rebooting under
a paused game - stayed dead afterwards: only a new open, stat or
listing mended a connection, and a stream held on to the one it had.
Each stream now keeps how to open its file again (share, path, and
its flags less create and truncate), and a read, write or seek that
finds its connection or its handle gone mends the connection, opens
the file again and goes on from its own record of the position, once.

A dropped context frees its files with it, in the built-in client as
libsmb2 documents for its own: struct smb2fh is the client's file, with
no wrapper of its own, and rsmb_free() frees the files still open. For
libsmb2, whose context does not, the handle's close is queued before
the dead context is let go, which answers it and frees it.
local_samba_test.sh restarts smbd under smb_idle_test, the read and
then the write first, over the built-in client and libsmb2.
2026-09-30 21:09:18 +02:00
LibretroAdmin e347535945 ci: package downloads are bounded and fall back to the main archive
Each download attempt gets four minutes; a stalled or failed one moves
the sources off the Azure mirror to archive.ubuntu.com before the
next.  The install then runs once from the downloaded packages.
2026-09-30 20:56:26 +02:00
libretroadmin 31877b2aed net/nfs: files stay usable across a server restart, a dropped link or a long pause
A game paused, or between disc reads, while its NAS rebooted found its
nfs:// files dead afterwards: the client never dialled again, a 4.1
session lost while idle was not set up again on the read path, and a
4.0 client whose lease ran out could not open its files again. A call
that fails now drops the connection and the next one dials afresh -
same identity, so a server that kept this client's state gives it back
- and reads, lookups and writes, which change nothing when repeated at
their offset, go again on the new connection; other calls fail once
rather than risk running twice. A lost 4.1 session is set up again, an
expired 4.0 client identifies itself again, and a file open for
writing whose state the server lost is opened again by its path.
Arguments built in the shared buffers are set aside before a dial,
which would otherwise send what the dial left there.

nfs_idle_test holds a file open for reading and one for writing across
a pause; the Ganesha matrix runs it past a short lease and across a
server restart for 4.0 and 4.1, and the unfs3 matrix across a restart
with the read and then the write as the first call after it.
2026-09-30 20:44:04 +02:00
LibretroAdmin 18a8f558ea gfx_thumbnail_preview: the thread stubs take rthreads.h's types
The audio driver header now includes rthreads.h, so the stubs must
match its declarations.
2026-09-30 20:41:34 +02:00
libretroadmin c9852fd7e3 net/nfs: NFSv4.1 and 4.2 sessions; version 4 takes the newest the server offers
The v4 client spoke 4.0 alone, which servers that have dropped it -
Windows Server among them - refuse. With version 4 chosen it now asks
for 4.2, then 4.1, and settles on 4.0 only when the server answers
neither. From 4.1 the client identifies itself with EXCHANGE_ID, opens
a session with CREATE_SESSION and says RECLAIM_COMPLETE, and every
request after that leads with a SEQUENCE on a session slot, which also
keeps the lease alive. Pipelined READs each take a slot of their own,
as many as the server grants. A session lost while idle is set up
again once; the server lets a session lapse with its lease when the
connection closes.

The Ganesha matrix runs every case against servers offering 4.0, 4.1,
4.2 and all three, each round on a fresh export, and checks the
version the client settled on.
2026-09-30 20:03:18 +02:00
LibretroAdmin 0da22ec5f9 ci: the menu harness job checks the lock budget
tools/lock_acquire_budget.sh runs on the release objects the job
already builds, next to the hot-function budget.
2026-09-30 19:56:47 +02:00
LibretroAdmin 7bd428f268 task_queue: a main-thread handler's wait sees a worker's task retire
Covered by the main-thread sample: the nested gather retires the
task, its callback runs once, and the handler goes on.
2026-09-30 19:56:44 +02:00
libretroadmin b0728d0177 command: LIST_CORES, LIST_PLAYLISTS and GET_PLAYLIST
A client could tell what was running but not find anything to run:
LOAD_CONTENT takes a core path and a content path and nothing reported
either. LIST_CORES gives the installed cores and their paths,
LIST_PLAYLISTS the playlists, and GET_PLAYLIST a playlist's entries -
index, label, content path, core path - 200 at a time with MORE <next>
to page on. All three are read-only commands on every interface and
tools for MCP clients.

The playlist commands read files, so they answer from a task: the text
is made on the task thread and sent from its callback through the
asking interface's reply_to, which holds an MCP request open until
then, and is dropped if the interfaces were re-created meanwhile.
2026-09-30 19:47:54 +02:00
LibretroAdmin fc80bee135 ci: package installs retry and have time for a slow mirror
apt-get install is retried up to three times with a fresh index, and
the install steps get 15 minutes (20 for the network clients' larger
set) instead of 5 or 8, which a slow mirror ran past mid-download.
2026-09-30 19:41:01 +02:00
LibretroAdmin 7e4b79ea54 Document the waits the frame loop still allows
Each is commented where it is, and CODING-GUIDELINES lists them.
2026-09-30 19:21:07 +02:00
LibretroAdmin 2edc9afbc6 content_load: the screenshot lane holds the I/O window across its reset
The window resets by the clock; a reset between the lane's hog and the
screenshot handed the screenshot a fresh window.
2026-09-30 19:21:00 +02:00
LibretroAdmin 4e9bff8d64 task_queue: a main-thread handler that waits does not run them again
The threaded gather a wait runs from inside a main-thread handler
leaves the main-thread tasks to the pass further up the stack.  They
ran twice and were freed twice: a load that failed with Threaded Tasks
on crashed falling back to the dummy core.
2026-09-30 19:20:56 +02:00
libretroadmin 89a1a78582 audio: the driver header takes the thread types from rthreads.h always
Without threads audio_driver.h declared slock_t and scond_t itself,
which rthreads.h declares with or without threads, so a file including
both - task_save.c - repeated the typedefs and make C89_BUILD=1
--disable-threads stopped there. The header includes rthreads.h in
every build; it holds declarations only. The compile matrix builds the
tasks and the audio driver that way, C89 and without threads.
2026-09-30 19:20:33 +02:00
LibretroAdmin 1b1b53d305 threaded-video harness: the ring-full lane runs until the driver has drawn its ten
The lane hooks the driver's frame() with a 30 ms present and checks
that every frame the video thread draws behind it is newer than the
one before. It also asked for at least ten such draws out of the 60
frames the core pushes - a race between this thread and the slow
present on whatever GPU is there: 32 draws on an M1, 10 on the CI
runner's Apple Paravirtual device in one run and 8 in the next, which
failed the macOS metal-harness job's XRGB8888 pass. The count says
nothing about the wrapper; the staleness of the draws does.

The core now keeps pushing after the 60 until the driver has drawn
ten, up to 600 more frames; a driver that never gets there is still a
failure. The grant and zero-copy counts stay measured over the first
60. Linux on Vulkan: 29 drawn, all lanes pass.
2026-09-30 19:16:42 +02:00
libretroadmin fabebb1d08 vfs/smb, vfs/nfs: the log says how much of a read read-ahead had ready
Whether a read-ahead window helps could not be seen from outside. Each
SMB and NFS stream with a prefetcher counts the octets it served from
its windows and those it missed and read directly, and adds them to the
backend's totals when it closes; once the core has closed its files
the frontend logs them - [SMB] Read-ahead: 812 of 900 KiB read came
from its window (90%) - and takes them back to zero. Nothing is counted
or logged while read-ahead is off.

The SMB and NFS thread tests read the test file through and check the
counts add up to it with read-ahead on and stay empty with it off.
2026-09-30 19:07:08 +02:00
LibretroAdmin a2020dca46 threaded-video harness: do not let the core pause for the window not being active
On macOS every lane that counts frames the core ran failed - the
zero-copy lanes saw 0 of 60 asks granted, the lent-window lane found
no grant and skipped, and "Paused" sat in the corner of the window
throughout. The harness window is never the active one: it is run
from a terminal, and under Cocoa the application is never made key.
pause_nonactive defaults on, so the frontend paused the core while
the window was inactive, and the core ran nothing; the lanes that
count what the driver presents (dupes count there) passed and hid
it. The harness config now turns pause_nonactive off. The same on
Linux, where the Xvfb window happened to report focus.
2026-09-30 18:57:08 +02:00
libretroadmin 48629e9b8f net/nfs: an NFSv3 open in a known directory is one round trip
NFSv3 resolved a path with a LOOKUP per component and then a GETATTR,
so each open three directories down cost five round trips - paid on
every file by content that opens many, as ScummVM does. Each
connection keeps the handles of the directories it walked through
recently, and a walk starts from the deepest one on its path: an open
in a known directory is a single LOOKUP, whose own attributes stand in
for the GETATTR. A handle the server calls stale empties the cache and
the walk starts again from the root; a rename, an unlink or a new
connection empty it too. NFSv4 already walks in one COMPOUND.

The client test counts the round trips of a repeated open three
directories deep, and opens through a renamed directory's old and new
names.
2026-09-30 18:48:43 +02:00
LibretroAdmin cb8c7cf34a threaded-video harness: put default.metallib next to the harness binary
The Metal driver loads default.metallib from the executable's own
directory - a bare binary has no .app bundle for newDefaultLibrary to
look in - and the tree's make builds it next to retroarch. The
harness binary lives in samples/gfx/threaded_video, so the driver
looked there and found nothing:

  [Metal] Could not load ".../threaded_video/default.metallib": library not found.
  [Metal] Failed to load main pipeline shader functions.
  [Video] Cannot open video driver. Exiting...

On the Cocoa build path build.sh copies the tree's default.metallib
next to the harness binary, and says so if the tree has none. clean
removes the copy.
2026-09-30 18:46:40 +02:00
LibretroAdmin 4b3ffffe37 threaded-video harness: on macOS, forget the argument AppKit filed as content
The harness's one argument is its cycle count. AppKit hands every
command-line argument that is not a flag to the delegate as a file
to open (application:openFiles:) while the application finishes
launching, before rarch_main() is reached; with no core up yet the
delegate stores it as the content path, and retroarch_main_init()
then tried to load a file named "1":

  [Content] Loading content file: "1".
  [Content] Could not read content file: "1".
  FAIL: retroarch_main_init failed

The Cocoa rarch_main() clears the content path before the lanes run.
2026-09-30 18:43:33 +02:00
libretroadmin 38d05171fb net/nfs: an NFSv4 export is found by the path v3 knows it by
Servers may root the v4 namespace below their export paths - Linux's
fsid=0, a Ganesha Pseudo of / - so /media exported as the root is "/"
to v4 and /media/user is "/user"; the export field, given the
directory's path on the server as for v3, found nothing. When the
export is not there and its first component is not in the namespace
either, that component is taken to lie above the v4 root and dropped,
down to the root itself. A first component the namespace does have
means the path is wrong, and it is refused as before.

The Ganesha matrix mounts the export by its server path under v4 and
checks a wrong path under an existing export is still refused.
2026-09-30 18:38:25 +02:00
LibretroAdmin 82cd1f00c8 threaded-video harness: build.sh rewrites the link line with patterns BSD sed takes, and checks what it built
On macOS the harness build "succeeded" and left no threaded_video_test:
the link line's -o was rewritten with \?, a GNU sed extension that
BSD sed treats as a literal, so the pattern never matched, the link
produced retroarch in the tree root, and the script said "built".
check-driver then ran ./threaded_video_test and got "No such file or
directory".

The rewrite is two plain patterns now (-o retroarch and
-o retroarch_debug), and build.sh checks every object and binary it
was supposed to produce, so a substitution a sed does not take fails
the build where it happened instead of at the run. The script was
run under BusyBox's POSIX sed, grep, awk and sh as well as GNU's on
the Linux tree; both build.
2026-09-30 18:34:39 +02:00
libretroadmin 21377ad1b8 vfs/smb, vfs/nfs: read-ahead is off unless chosen, and off is off
SMB and NFS read one request per read again unless a read-ahead is
set: both default to 0, and 0 now means off. It had meant 1 MiB - the
backends substituted their window for it and still started a
prefetcher on every read-only open, each holding a second connection
and a thread, which content that opens many files at once paid for on
every open. With read-ahead off no prefetcher starts; with it on, both
behave as before. The ranges start at 0, NFS's in SMB's 64 KiB steps.

The SMB and NFS thread tests run with read-ahead off and on, checking
a read-only open starts a prefetcher only when it is on, and the menu
harness checks both settings can be set to 0 and default to it.
2026-09-30 18:33:26 +02:00
LibretroAdmin 92dc759b3b threaded-video harness: run on macOS, inside Cocoa's own main, and on the Metal driver in CI
The harness did not build on macOS. There the frontend's main() is
Cocoa's (HAVE_MAIN, ui_cocoa.m): it stands up NSApplication, the
delegate and the window, and calls rarch_main() from
applicationDidFinishLaunching:, and the Metal and Cocoa GL contexts
need all of it. build.sh replaced retroarch.c's main() with the
harness's own, which on macOS is a second main() against ui_cocoa.o
and an application that was never started.

On a tree whose compile line carries HAVE_MAIN the harness now keeps
Cocoa's main and takes rarch_main() over instead (harness_cocoa.m,
compiled with the tree's own Objective-C line; retroarch.c gets its
rarch_main renamed, the harness's main becomes harness_main): the
lanes run in rarch_main's place, on the main thread with the
application and window up. Each frame turns the main run loop once,
so the window's layer tree is committed and the drawables the driver
presents come back round. And the process leaves with the harness's
status: the Cocoa companion's deinit, reached from main_exit(),
terminates the application, which is exit(0), so an atexit handler
installed once the status is known ends the process with it. Nothing
changes off macOS - the hooks are under HAVE_COCOA and the other
build path is as it was. check-driver no longer wants $DISPLAY on
Darwin.

MacOS.yml gets a metal-harness job: a Makefile build without Qt on
the runner, then the harness on DRIVER=metal with the core pushing
RGB565 and again XRGB8888 - every lane the Vulkan and D3D legs run,
including the lent-window lane over the Metal lend of 771a19c8a1,
on the runner's GPU. It is the first end-to-end run of the Metal
driver anywhere in CI.

build.sh also says why it could not find the compile line when it
cannot: the usual reason is a config.mk that fell behind configure
after a pull ("config.mk is outdated or non-existing"), which make
reported and the script threw away - that was the failure reported
against it today.

The Linux path was rebuilt and rerun on Vulkan under ASan with this:
all lanes pass. The macOS path is compiled and run only by the new
CI job; no Apple toolchain here.
2026-09-30 18:30:49 +02:00
LibretroAdmin 27c7115d33 samples: nothing builds against the removed nbio
The PNG samples read their files back with filestream_read_file; the
nbio sample goes with the code it tested.
2026-09-30 18:21:23 +02:00
libretroadmin 1d32702659 menu: a value picked from a setting's list is the value listed
A setting's list starts at its minimum, but a pick was turned back
into a value from 0 (plus offset_by, which few settings set to their
minimum), so every range above 0 was a step short: NFS Version 4 set
1, an SMB timeout of 20 set 19, a read-ahead of 64 set 0, Minimum
Audio Latency and the rest likewise. The pick is taken back from the
minimum in whole steps, exactly as the list is built. The network
settings harness picks through the real dropdown to hold it there.

The SMB timeout goes to 60 seconds as NFS's does, and NFS Network
Settings gets the icon SMB Network Settings has in Material UI.
tools/console_objects_check.sh, run by the compile matrix, reads what
each console Makefile builds and fails on the MCP server anywhere or
the crypto library on GameCube, Wii and PS2.
2026-09-30 18:14:08 +02:00
LibretroAdmin dbc19b9b2f content_load: the close-waits lane is not bounded by the machine's speed
Under TSan the save takes some five hundred frames of the I/O window,
past the lane's 400; the bound now only stops a close that never ends.
2026-09-30 18:11:07 +02:00
LibretroAdmin 1986e2fe7f screenshot: encode a slice at a time from the frame loop
With Threaded Tasks off the whole deflate ran in one handler call on
the frame thread.  A cancelled screenshot leaves no partial file.
2026-09-30 18:11:03 +02:00
LibretroAdmin 4d04a1d3e6 rpng: encode in steps
rpng_encode_begin/step/free write the same file as the one-go encode,
a bounded number of rows at a time.
2026-09-30 18:11:01 +02:00
LibretroAdmin 1d17be24ab metal: lend the core a framebuffer, and read a frame pushed from it out of the loan
The Metal driver was the one desktop driver with no
get_current_software_framebuffer: a core's ask was declined, it
rendered into its own buffer, and every frame was copied into the
texture by the host (replaceRegion:). Vulkan and D3D12 lend; the
threaded wrapper lends; a core written for the loan (beetle-psx's
software renderer since 3f5be07) got nothing from it on macOS with
threaded video off.

FrameView lendFramebuffer: hands the core the contents of a
shared-storage MTLBuffer sized to its ask, in the frame's pixel
format - BGRA8 for XRGB8888, and R16Uint for RGB565, which is what
_src is, so the existing compute conversion applies unchanged - with
a linear texture over the buffer at the tight row pitch. On Apple
silicon that is the memory the GPU samples. updateFrame:pitch: then
tests the pushed frame against the loan the way vulkan_frame_window
and d3d12_sw_fb_window do (7510039d5d): the whole loan, or a window
into it at the loan's pitch and whole, is blitted out of the loan
texture with a source origin into the frame texture (or into _src
for RGB565), on the blit command buffer the context commits ahead of
the frame's own - one GPU copy, no host copy, and the cached frame's
pointer into the loan stays valid. The lend waits for the command
buffer of the last such blit before handing the buffer back to the
core, since the core is about to write over what the GPU read; a
resize, and the view going away, retire the cached frame first.

Declined, as the Vulkan and D3D12 lends decline, when the row pitch
the device needs for a linear texture (its minimum linear texture
alignment, at least 16) is wider than width * bpp - a core that
ignores fb.pitch would shear - and for a 10-bit source, which has no
such texture. At an alignment of 16 the widths cores use (320, 640,
beetle-psx's 700) all lend at the tight pitch in both formats.

samples/gfx/metal_texture_update gets a third lane, run on the macOS
CI leg's Metal device: for those widths in both formats, the shared
buffer and its linear texture at the tight pitch, a CPU-written
pattern, a window blitted out with an origin and read back at its
four corners, and a second write after the first blit's command
buffer completed that the second blit must show - the ordering the
lend relies on. The three counters of 7510039d5d cover this path too,
and the threaded-video harness's lent-window lane runs on it with
DRIVER=metal on a Mac.

Compiled and run only by CI here (no Apple toolchain on this box).
2026-09-30 18:01:26 +02:00
libretroadmin 9ffdab15a3 network: the MCP server is a desktop build option, HAVE_MCP
The MCP server was listed with the networking objects, so every
platform Makefile with networking compiled it, the consoles included,
and the 3DS - networking without the command interface it is built on
- failed to. It is HAVE_MCP now: a ./configure option, on by default,
dropped without networking, and built only with the command interface;
no console Makefile sets it. The server, its settings and its menu
entries all follow it.
2026-09-30 17:59:49 +02:00
LibretroAdmin bc3fde8e1d oga: a frame pushed as a window into the driver's own loan is blitted out of the loan
A core that renders into the buffer GET_CURRENT_SOFTWARE_FRAMEBUFFER
lends it may push back a pointer partway into that buffer, at the
buffer's pitch, with the size of the window it wants shown: that is
how an overscan crop is done by offset, and beetle-psx does exactly
that since 3f5be07. The threaded wrapper has accepted such a push as
a lend since 9ed0db58c3, the vulkan and d3d12 drivers since 7510039.
This driver lends frame_surface->map and knew a lent frame only by
that exact pointer, so a window into it took the copy path: memcpy
from the surface onto itself, one row pitch apart, shredding every
row the copy overtook, and then blitting from (0,0) so the crop was
ignored as well. Most visible on a paused frame or a screenshot,
where the frontend replays the cached window pointer.

Recognise the window the same way the other two drivers do: inside
the mapping, on the mapping's pitch, starting on a pixel. Then skip
the copy and blit from the window's origin, clamping the size to
what the surface holds from there. A pointer inside the mapping that
is not addressable as a window (another pitch, or mid-pixel) is not
copied either -- that would be the same self-overlapping memcpy --
the loan is shown from its origin instead.
2026-09-30 17:40:46 +02:00
libretroadmin de1fe72c69 network: an MCP server, the command interface for AI assistants
RetroArch serves the Model Context Protocol over Streamable HTTP as one
more command interface beside UDP and stdin, polled with them and
waiting on nothing. Its tools are the commands in command.h, listed
from the rows HELP prints with their read-only and destructive hints,
and a tool call runs the command's own handler; a command that answers
later holds its connection until it does. Both protocol eras are
answered: 2026-07-28 statelessly, with server/discover and the header
checks, and earlier revisions through initialize.

It is off by default and listens on 127.0.0.1. Every request carries a
bearer token, made on first start and kept in the keychain, and a
browser Origin other than this machine is refused. MCP Server, its port
and its token are under Settings > Network; docs/mcp-server.md covers
connecting a client.
2026-09-30 17:36:04 +02:00
libretroadmin 21890c064a Remove unused nbio code 2026-09-30 17:24:26 +02:00
libretroadmin 3321c7cb5a tasks: the keychain passphrase task is declared without networking too
task_push_keychain_passphrase() was declared inside the networking-only
part of tasks_internal.h, so a build with the keychain and without
networking called it from the menu undeclared. It is declared under
the keychain's own gate. The compile matrix builds the menu and the
task in that shape with implicit declarations as errors.
2026-09-30 17:15:30 +02:00
LibretroAdmin 877823df48 threaded_video: fast-display counts drains and cadence instead of timing
A stalled machine drains once per stall and moves a few frames; the
defects the lane is for drain every fifth frame or move every one.
2026-09-30 17:06:23 +02:00
LibretroAdmin 82a0f019dd video_thread: no catch-up burst after a stall
The hold measured from the vblank the video thread expected at its
last present.  After a stall that vblank had passed, and every frame
was released at once until the presenter caught up: the core ran at
the display's rate.  It is carried forward on the display's grid.
2026-09-30 17:06:19 +02:00
libretroadmin 2790aa0cce command: every command describes itself; HELP lists them
Both command tables carry a one-line description and flags saying
whether a command only reports or may lose data or load code, so the
tables in command.h are the one description of what RetroArch can be
told to do. HELP answers with every command - name, argument, what it
does, [read-only] or [destructive] - or with one when named; interfaces
that hand the commands to a client can be generated from the same rows.
tools/command_table_check.py, run by CI, fails on a command added
without a description.

A command sent without an argument no longer has its argument pointed
one byte past the end of the line, and SHOW_MSG is documented as
taking the message it shows.
2026-09-30 09:43:00 +02:00
libretroadmin 684be71938 menu: sublabels for the NFS settings, SMB read-ahead and Kerberos, TLS verify
Every NFS setting, SMB read-ahead, the SMB Kerberos realm and KDC, and
TLS certificate verification had help text but nothing that bound it
to the entry, so they showed without a sublabel. They are bound in the
sublabel map, each under the gate its setting is built under.
2026-09-30 09:24:07 +02:00
LibretroAdmin 2a17334ad2 vulkan: parking a texture allocates nothing and cannot fail
A texture released through vulkan_unload_texture is parked for the
frame thread to destroy once a swapchain's worth of frames has gone by,
because a frame already handed over, or menu code still looking at the
handle, may yet use it. Parking allocated a list node per texture, and
when that allocation failed the texture was destroyed on the spot,
after waiting for the frames already submitted - which covers neither
of those two.

The link and the countdown are in struct vk_texture now. Parking is the
compare-and-swap push it already was, on the texture itself: no
allocation, so no failure, so no path that destroys early or waits.
vulkan_texture_defer() loses the same fallback, and every unload loses
a malloc and a free.

What is left is vulkan_texture_defer_copy(), which parks a texture
embedded in another object (the font's two) by moving it to a heap
copy; if that copy cannot be allocated it still waits on the driver's
own fences and destroys. That runs on the frame thread.

Two comments that still described a vkQueueWaitIdle under queue_lock in
the unload path are brought up to date; there has been none for a
while.

There is no test that fails before this and passes after: the path it
removes needed an allocation to fail. The threaded-video harness lanes
that load and unload textures pass on lavapipe under the validation
layer (software core, hardware core, hardware core with its own
device), and a running RetroArch with the ozone menu toggled eight
times and quit, threaded video on and off, reports nothing - a texture
destroyed early or never would show there.
2026-09-30 09:09:08 +02:00
libretroadmin 2a4d05984e docs: the built-in network stack and the keychain
docs/network-stack.md describes what users meet of the built-in TLS,
SMB and NFS clients and the keychain: certificate verification, the
SMB and NFS settings and content addresses, read-ahead, where saved
passwords live, and how to move a keychain to another machine with a
passphrase; with the configure options that shape the stack. CHANGES
lists the stack under NETWORK.
2026-09-30 09:07:59 +02:00
libretroadmin f0ec62cd61 keychain: a passphrase lets the keychain move to another machine
The keychain's key comes from the key file and this machine's identity,
so a configuration carried to another machine lost every saved
password. Settings > User > Keychain Passphrase wraps that same key
under a passphrase in the key file, next to a wrap for this machine;
nothing is sealed again, and a key file without one is unchanged. On
another machine the keychain comes up locked, saves write nothing in
the clear, and entering the passphrase once unlocks it, wraps it for
the new machine and puts the credentials back into the running
settings. The key derivation runs as a task a slice at a time, so the
main thread never waits on it, with or without threads.

A sealed value that cannot be opened is now taken out of what the
loader sees even when there is no key at all, instead of becoming the
setting's value; and only the values that failed to open are carried
through a save, so clearing a password reaches the file and a normal
save no longer reports credentials from another machine.
2026-09-30 08:45:30 +02:00
LibretroAdmin 6d0a54a08c threaded_video: pacing-drain counts frames instead of timing them
Frames the wrapper replaced because the display ran late are no longer
a failure: every frame must be drawn or replaced, and nothing else.
2026-09-30 08:45:02 +02:00
LibretroAdmin b82a139538 explore: title the index build task
The slow-handler watchdog names a task by its title; this one was
reported as untitled.
2026-09-30 08:44:58 +02:00
libretroadmin f508e065c6 crypto: the AES tables are constants; nothing is set up on first use
The AES round tables, the inverse S-box and the InvMixColumns table
were built on first use behind a plain flag. Two threads setting up
keys at once both wrote them while each read the flag - a data race
and, on a weakly ordered CPU, a thread that sees the flag before the
tables and encrypts with half of them. They are now constants generated
from the S-box, so there is nothing to set up; the AES-NI check caches
its answer atomically.

crypto-tsan-check first uses every primitive with a table or a CPU
dispatch from eight threads released together, under TSan, and checks
they all compute the same bytes. It runs in the libretro-common tests
workflow and reports this race on any runner, where the SMB test saw
it only when two connections started together on two cores.
2026-09-30 08:23:37 +02:00
LibretroAdmin effa709984 vulkan: the emulated mailbox takes no lock
Emulated mailbox - FIFO kept while fast-forwarding with vsync on, with
a second thread doing the acquiring so the presenting thread never
blocks on the display - passed a request and an answer between its two
threads under a mutex, with a condition variable that carried the
request, the answer and the teardown all at once.

Each of the three is a word of its own now, written by one side and
taken by the other, and each direction has an eventcount:

  request   the presenting thread wants an image; the acquire thread
            takes it and acquires
  acquired  the acquire thread has an answer. result and index are
            written before it is raised, and not again until the
            presenting thread has lowered it and asked once more
  dead      teardown

The acquire thread sleeps on `work` and looks at request and dead again
between announcing the wait and committing to it; the blocking acquire
on the presenting side sleeps on `answered` the same way, against the
one deadline it already had. Whether a request is outstanding was only
ever the presenting thread's business and is a plain bool of its. The
finite waits that let the acquire thread notice the teardown are as
they were.

Nothing the driver does is different: same requests, same answers, same
bounds. This was the last lock in the Vulkan code that was not
queue_lock.

samples/gfx/vulkan_validation had no case for the mailbox at all. It
has one now: emulation switched on in place, forty frames drawn through
it under the validation layer, switched off with a request possibly in
flight - which must end with an image in hand and the acquire thread
joined - three times over, the last torn down with emulation on. It
passes on the mutex version and on this one, and on this one under
ThreadSanitizer.

samples/gfx/vulkan_mailbox_init_leak keeps verbatim copies of the
mailbox's init and deinit, with the primitives mocked, to hold the
partial-init leak fix in place; the copies and the mocks follow: the
failure stages are the first eventcount, the second, and the thread.

tools/lock_acquire_budget.sh measures the acquire thread (none), the
present (queue_lock, once) and the acquire (queue_lock, only in the
path that empties a full stale-acquire list). Against the code before
this change the first reads four and the last two.

With vsync on, fast-forward toggled six times in a running RetroArch
with Beetle PSX HW, threaded video on and off: emulation entered and
left three times each, no validation error.
2026-09-30 08:23:23 +02:00
LibretroAdmin b6f41430e0 command: answer PLAY_REPLAY_SLOT from the frame loop
The command returns at once; the reply follows once the movie task has
installed the replay, to the sender that asked.  command.c joins the
no-wait guard.
2026-09-30 07:55:25 +02:00
LibretroAdmin 4edb7a8ab9 natt: drop the blocking variants
No caller blocks: the NAT task steps each request itself.  network/
joins the no-wait guard.
2026-09-30 07:55:22 +02:00
LibretroAdmin b4098a2fb4 vulkan: the last drains outside the teardown go, and queue_lock guards only the queue
Three idle waits were still on the idle-wait allowlist, one list
borrowed queue_lock because it was the lock at hand, and the present
held it across its own failure handling. queue_lock is the
one lock this driver cannot lose - the libretro interface hands it to
hardware cores, and a VkQueue shared by two threads needs it - so what
it guards should be the queue call and nothing else.

  vulkan_submit_deferred_cmd_fenced
      With no node or fence to defer an upload with, it submitted and
      then called vkQueueWaitIdle, under queue_lock. That drains a
      hardware core's work too and keeps the core out of lock_queue for
      as long as the GPU takes. The upload is waited for on a fence now
      - the caller's, else one the driver keeps for the purpose - with
      the lock dropped after the submit.

  vulkan_acquire_next_image
      With the stale acquire list full it called vkDeviceWaitIdle and
      destroyed a semaphore. That is a use of every queue made without
      queue_lock on the presenting thread, while a core may be
      submitting; it waited on the core's work; and an acquire is not
      queue work, so it never waited for the signal it destroyed the
      semaphore under. One batch that waits on the whole list, with a
      fence, consumes the signals; the lock is held for the submit call.

  vulkan_present
      Kept queue_lock from the present call to the end of the function,
      across the failure handling. A failed present tears the swapchain
      down, the teardown waits for the present with an empty fenced
      submission behind it, and that submission takes queue_lock: where
      the mutex is not recursive - everywhere but Windows - the thread
      waited on itself, and an out-of-date present never returned. The
      lock is dropped when vkQueuePresentKHR returns.

  slang_chain_flush
      The vkDeviceWaitIdle fallback for a chain created without
      wait_submissions. Every chain in the tree is created with it. The
      callback is required now and the fallback, with the chain's copy
      of the lock callbacks that only it used, is gone.

  the parked-texture list
      vulkan_unload_texture parks textures for the frame thread to
      retire, and a reinit fallback can park one from the main thread,
      so the list was serialised - with queue_lock, which made parking
      a texture wait behind a present or a core's submit. Parking is a
      compare-and-swap push onto one atomic pointer now, and the list
      the frames count down is private to the thread that records
      them, which takes what was pushed with an exchange each tick.

tools/vulkan_idle_wait_allow.list is down to its one legitimate entry,
the device teardown; the existing idle-wait check fails on the three
removed sites if any comes back.

samples/gfx/vulkan_validation gains two cases. A present that fails:
the driver has to come back from it with the swapchain torn down. The
code before this change never returns from it on Linux (the test gives
it ten seconds). And the acquire case: frames presented
for more than two lists' worth without anything waiting on their
acquires. The driver has to get through it without one queue or device
drain, and without the validation layer objecting to the batch that
consumes the list or the semaphores destroyed behind it (the layer's
MissingAcquireWait on each such present is the scenario itself, and is
set aside by name). The code before this change drains 19 times there;
this one, none.

The threaded-video harness passes on lavapipe under the validation
layer with a software core, a hardware core, and a hardware core that
creates its own device.
2026-09-30 07:45:45 +02:00
LibretroAdmin fbff529434 vulkan: the filter chain no longer creates mirror-clamp samplers
The slang filter chain makes one sampler per filter, mip filter and
address mode. The address modes had a fifth entry, mirror-clamp-to-edge,
that no wrap mode maps to - a preset can name border, edge, repeat and
mirrored repeat, and rarch_wrap_to_address() returns one of those four -
so its four samplers were created on every chain and never bound.

Creating them is only valid with VK_KHR_sampler_mirror_clamp_to_edge
enabled. The frontend adds that, as an optional extension, to a device
it creates itself. A core that creates the device through the
negotiation interface's create_device is handed only the required list,
so on every such core (Beetle PSX HW, for one) each chain raised

  VUID-VkSamplerCreateInfo-addressModeU-01079
  VUID-VkSamplerCreateInfo-addressModeU/V/W-parameter

four times over. Drivers tolerate it; it is invalid all the same.

The entry and its case are gone. The optional extension stays in the
device list: nothing in the tree uses it now, but a core drawing on the
frontend's device may.

The threaded-video harness core can now be that kind of core:
HARNESS_CORE_HW_VULKAN=2 registers a version 1 negotiation interface
and creates the device with exactly what the frontend passes. The
Vulkan validation workflow runs the harness that way as well. Before
this change that run reports 48 validation errors and fails; with it,
none.
2026-09-30 06:49:47 +02:00
libretroadmin 4e6885d5a3 vfs/smb: libsmb2's list of live contexts is only touched under a lock
libsmb2 keeps every context on one process-wide list that
smb2_init_context() pushes to, smb2_destroy_context() unlinks from and
smb2_context_active() walks, with no lock of its own. Connections made
and dropped on several threads at once can lose an entry, after which a
live connection reads as dead and its seeks and reads are refused, or
break the list outright. Every one of those three calls in the backend
now goes through a wrapper holding a lock for the list operation alone;
none of them does network I/O. The cleanroom client has no such list,
and single-threaded builds none to guard: both call straight through.

The libsmb2 lane runs the SMB tests once more under TSan against a
TSan-built libsmb2, which reports this on any runner rather than only
when two cores happen to collide.
2026-09-30 06:48:39 +02:00
LibretroAdmin 3db41c4d27 task_queue: count due main-thread tasks in the progress walk
The gather already holds running_lock to publish progress; counting
the due main-thread tasks there saves the frame a second acquisition.
2026-09-30 06:16:46 +02:00
LibretroAdmin 1c6de25fd4 content: watchdog every frame-thread task, load stages excepted
Debug builds report slow handlers with Threaded Tasks on as well.  The
load stages that run the core's own load and the driver rebuild are
titled and exempt: they are one-off by design.  The content_load
harness gains the acceptance run - menus, playlists, overlays, the
Explore index and a load, with no handler over a frame.
2026-09-30 06:16:42 +02:00
LibretroAdmin dc321182c8 explore: name the release month category
It read "null (1 - 12)" on the top Explore screen.
2026-09-30 06:16:39 +02:00
LibretroAdmin d3149eb217 task_queue: time main-thread handlers on the threaded runner
The slow-handler watchdog now also measures RETRO_TASK_FLG_MAIN_THREAD
handlers when Threaded Tasks is on: they run on the frame thread there
too, so an overrun is the same visible stall.
2026-09-30 06:16:35 +02:00
libretroadmin 725016e00d vfs/smb: each directory listing returns entries of its own
retro_vfs_readdir_smb handed every caller the same function-static
entry, so listings on different threads overwrote one another's name
and type while the caller copied them out. The entry now lives in the
directory handle, for the cleanroom client and libsmb2 alike; the
libsmb2 threads test is clean under TSan with it.

vfs_threads_test names each failed operation with its thread, round
and what came back, so a failure on a runner says where it happened
rather than only how many.
2026-09-30 06:05:24 +02:00
LibretroAdmin b7b0920836 video: the hardware ring's fence is handed between threads lock-free; no lock in the Vulkan fence
7a8b2ab made the Vulkan ring fence re-signalable by giving each one a
mutex and an "armed" flag. That was a lock added to a path that is
meant to lose its locks. It is gone, along with everything else that
commit added to vulkan.c: the driver's ring fence is a bare VkFence
again.

What the lock was for is done in the wrapper instead, once, for every
driver, without one (gfx/video_thread_hw_fence.h). A slot has three
driver fences and each is only ever touched by the thread that owns it
at that moment; ownership moves by an atomic exchange:

 - the video thread, after a frame drawn from the slot, takes an idle
   fence, signals it and exchanges it into `armed`. If an earlier
   arming comes back out - a dupe: nobody waited it - the video thread
   owns that one, waits it out and returns it to idle;
 - the core's thread exchanges `armed` out, waits what it got, returns
   it to idle, and looks again.

At most one fence is armed and at most one is with the waiter, so one
of three is always idle. A driver's signal is only ever called on an
idle fence and its wait on a signalled one.

That also closes the same hole in the GL drivers, which needed no
change: gl2 and gl3 kept one sync object per slot behind a bare
pointer that the video thread deleted and replaced while the core's
thread could be waiting on it and then deleting whatever the pointer
held. And it retires the slot's in_flight flag, a plain bool written
by one thread and read by the other.

samples/gfx/hw_ring_fence runs the real header with two threads at
one slot and fake fences that count a signal on a fence not idle, a
wait on one never signalled, and two calls inside one fence at once;
under ASan+UBSan and under TSan. The single re-signalled fence it
replaces is built too and must fail. The Vulkan hardware-core lane of
the threaded-video harness (dupes in game and under the menu) passes
on lavapipe with the validation layer, as it did with the lock.
2026-09-30 05:48:15 +02:00
libretroadmin 6fe0b87b2e removed_member_check: leave the pruned trees out of the diff too
The check scans for readers with deps/ and the other vendored trees
pruned, but built its set of removed members from the whole diff, so
deleting a bundled library counted every member that library declared
- tv_sec, ss_family, session and the rest - as removed RetroArch API,
and the removal of deps/mbedtls, deps/bearssl-0.6 and deps/libsmb2
failed on 430 readers of the platform's own structs. The pruned trees
are excluded from the diff as they are from the scan: a vendored
library's members are its own API, read through its own headers. A
member removed anywhere else is still caught, readers and all.
2026-09-30 05:18:25 +02:00
LibretroAdmin 7a8b2ab26f vulkan: a dupe of a hardware frame may signal its ring fence again; hardware-core lane
The regression lane for 53ca1ac (a hardware core's image is let go of
before the core destroys it), and the second bug the lane ran into.

The bug. Under the threaded wrapper the video thread signals a ring
slot's fence after every frame it draws from the slot, and a dupe
draws from the slot last presented again: one frame in two for a
30 fps game, every frame while the menu is up over a hardware core.
The fence was a bare VkFence, signalled by an empty vkQueueSubmit, and
nothing reset it between a frame and its dupe, so every dupe submitted
a fence that was already signalled or still pending. That is invalid
usage (VUID-vkQueueSubmit-fence-00063/-00064); lavapipe aborts on it.

The ring's fence now records whether it is armed. A signal that finds
it armed waits the earlier arming out and resets it first - a wait on
work submitted a frame ago - and a lock orders that against the main
thread's wait-and-reset, so a reset cannot land on a fence the other
thread has just submitted. Direct3D 12's ring fence counts and never
had this.

The lane. The threaded-video harness core gains a Vulkan hardware
mode (HARNESS_CORE_HW_VULKAN=1): one image of its own, set_image and a
hardware frame per retro_run with every third a dupe, destroyed in
context_destroy without a word to the frontend, and an export that
withdraws it with set_image(NULL) and destroys it in mid-session. It
calls Vulkan through the frontend's interface and links nothing.

With that set the harness runs one lane, hw image lifetime:

 - unthreaded: frames in game, then a staged close from the menu;
 - the core again, under the wrapper: hardware frames reach the
   driver; the core withdraws and destroys its image, and no install
   names an image until it presents again; then the staged close, and
   no install names an image after the core's context_destroy.

It also requires that the close presented at least one frame between
the core's teardown and the new drivers, so it cannot pass by no
longer testing the window.

Run in the Vulkan validation job on lavapipe:

   with both fixes                 pass, no validation errors
   53ca1ac taken out               VUID-vkDestroyImageView-imageView-01026
                                   (view in use), then a segfault
   this fence change taken out     VUID-vkQueueSubmit-fence-00063,
                                   then lavapipe aborts

The software-core lanes are unchanged and pass on the same build.
2026-09-30 05:15:22 +02:00
libretroadmin f516093bc4 deps: drop the bundled mbedtls, BearSSL and libsmb2; library backends by option
Every build uses the built-in TLS, SMB and NFS clients, so the
bundled deps/mbedtls, deps/bearssl-0.6 and deps/libsmb2 are removed.
A system library can still stand in for a built-in client, off by
default: --enable-mbedtls and --enable-bearssl replace the TLS client
with a system mbedTLS or BearSSL, and --enable-libsmb the SMB client
with a system libsmb2; a library asked for and not found is a
configure error, and two TLS libraries at once are refused. The
older builtin/system switches go, as do the platform files' bundled
selections, and the libsmb2 backend follows the library's current
share-enumeration API.

Linux-library-net-backends.yml replaces the legacy lane with one lane
per library: each configures with its option, builds RetroArch to
the link, checks the library's backend is linked and the built-in
client is not, and operates it - the cheevos login transport over a
local HTTPS server for mbedTLS and BearSSL, the SMB VFS tests against
a local smbd for libsmb2 (built from upstream; Ubuntu has no package).
The cheevos_login sample links the system BearSSL, the smb_backup
sample carries stand-in libsmb2 headers for its fake share, and the
samples workflow installs libbearssl-dev.
2026-09-30 05:01:22 +02:00
LibretroAdmin 53ca1ace34 video: a hardware core's image is let go of before the core destroys it
Closing content with a Vulkan hardware core under threaded video could
freeze or crash the frontend inside the GPU driver.

A staged content close keeps the video driver up after the core is
gone and presents dupes of the last frame until the next session's
drivers are built. For a hardware core the last frame is the core's
own image, which the core destroys in context_destroy. Nothing told
the driver: video_driver_free_hw_context() called context_destroy and
left the driver holding the image, and every dupe after it sampled a
destroyed VkImageView. Seen on NVIDIA as a fault inside the driver,
called from the filter chain's final pass on the video thread, with
the main thread waiting on that frame in the ring drain.

Under the wrapper two more things made it certain rather than likely:

 - the wrapper had no invalidate_hw_render_cache, so the runloop's
   invalidate (core_reset) stopped at the wrapper and never reached
   the driver;
 - a core's set_image(NULL) - how a core withdraws its image before
   destroying it, and what LRPS2 does in context_destroy - only wrote
   the ring slot the core fills next. A dupe re-reads the slot last
   presented, so the withdrawal never took effect.

Now:

 - video_driver_free_hw_context() invalidates the driver's hardware
   render cache on both sides of context_destroy: before, so nothing
   is drawing from the image while the core tears it down, and after,
   because a core may present once more on its way out;
 - the wrapper implements invalidate_hw_render_cache: drain the frame
   ring, have the driver wait out its submissions and drop the image
   on the video thread, then clear the ring's slots and the slot a
   dupe would re-read;
 - set_image(NULL) under the ring does the same.

A dupe after that draws the driver's blank texture, as it already does
without the wrapper after a reset.
2026-09-30 04:43:21 +02:00
LibretroAdmin 1fda0518ef netplay: the deferred host setup frees its state
The task carrying the deferral's deadline had no cleanup, so its
state leaked once setup ran; LeakSanitizer on the content_load
harness's host-setup lane reports it.
2026-09-30 04:43:19 +02:00
LibretroAdmin 38c605ae44 netplay: host setup waits for the relay query from the frame loop
Hosting through a lobby relay needs the relay's address, and host
setup waited for the lobby's answer inside CMD_EVENT_NETPLAY_INIT - up
to ten seconds on a slow lobby.  When the query is still out, the
command now puts host setup off to a main-thread task that checks each
frame and reissues CMD_EVENT_NETPLAY_INIT once the answer is in, or
goes direct once the bound has passed; the frame loop keeps running.
netplay_mitm_query no longer waits at all.

The room reset at the start of host setup also cleared an answer
already in hand, so the prefetched query was asked - and waited for -
again every time; the answer is now carried across the reset.

content_load harness lane: with a relay configured and the query out,
the command returns in microseconds with host setup queued, frames
keep presenting, and setup runs once the query settles.
2026-09-30 04:16:57 +02:00
LibretroAdmin e5b3b3f7ea CI: the menu-path wait guard covers the content load; its harnesses run
The guard now greps tasks/task_content.c for task_queue_wait, forbids
the close's blocking helpers (content_wait_for_*_state_task, which
wait inside task_save.c where the first grep cannot see) anywhere in
menu/ and tasks/, and checks the load still closes through
retroarch_main_deinit_pending.  The menu-harness job builds and runs
samples/tasks/content_load and samples/menu/explore_build on its
non-Qt menu build.
2026-09-30 04:16:54 +02:00
LibretroAdmin 02c01f02e0 threaded-video harness: run the lent-window lane on the path each driver lends
The lent-window lane merged in 7510039d5d failed where a driver lends
nothing: d3d11 has no framebuffer to lend, and d3d12 lends only for a
row pitch on a 256-byte boundary, which the harness core's RGB565 at
320 wide (640 bytes) is not - so on both Windows legs the core was
granted nothing and the lane failed on the grant count. A driver that
lends nothing has no loan to push a window into: the lane now reports
a skip there instead.

That also left the D3D12 half of 7510039d5d - the window box copied
out of the loan's footprint - with nothing running it. The harness
core can push XRGB8888 (HARNESS_CORE_XRGB8888 set at start; the pixel
values are the same in the low bits either way, so every lane reads
as before): at 4 bytes a 320-pixel row is 1280 bytes, on the boundary,
and d3d12 lends. The d3d12 matrix entry runs with it. The Vulkan legs
run the harness under both formats, since the two take the driver's
two upload paths - RGB565 through the compute conversion of a staging
buffer, XRGB8888 through a linear image sampled directly and, for a
window, vkCmdCopyImage out of it; the second is the one beetle-psx
takes. Both pass on lavapipe under ASan with the validation layer
silent: 59 grants, 39 windows, 1 host copy (the core's own oversize
frame), 3 frame textures over 60 frames, and the paused redraw runs
through.
2026-09-30 04:16:41 +02:00
LibretroAdmin 7510039d5d vulkan, d3d12: a frame pushed as a window into the driver's own loan is read out of the loan
A core that renders into the buffer GET_CURRENT_SOFTWARE_FRAMEBUFFER
lends it may push back a pointer partway into that buffer, at the
buffer's pitch, with the size of the window it wants shown: that is
how an overscan crop is done by offset, and beetle-psx does exactly
that since 3f5be07 - the whole 700x480 scanout surface is rendered
into the loan and the visible part pushed. The threaded wrapper has
accepted such a push as a lend since 9ed0db58c3. The drivers' own
loans, the ones handed out with threaded video off, did not.

The Vulkan driver knew a lent frame only by the exact pointer of its
swapchain-index texture's mapping, so a window into it was a frame
of another size than the texture: the texture was recreated at the
window's size every frame (and again at the loan's size at the next
lend), the loan went to the deferred list, and the window was copied
out of the parked mapping by the host. That worked while the core
kept pushing. The cached frame kept naming the parked mapping,
though, and when the core stopped - the menu opened over a paused
core, or the window lost focus with pause-on-focus-loss on - every
iteration redrew the cached frame; after the deferral window the
mapping was unmapped and freed, and the next redraw read it. That is
libretro/RetroArch#19652 (Beetle PSX on an RTX 4060: memcpy fault
from vulkan_frame's row copy on entering core options or on losing
focus).

vulkan_frame() now tests a pushed frame against the texture's mapping
(vulkan_frame_window): inside it, on its pitch, whole, word-aligned.
Such a window leaves the texture alone - it keeps the loan's size for
the next lend and its mapping stays mapped, which is what the cached
frame needs - and is cut out on the GPU into texture_optimal, which
is what gets sampled: vkCmdCopyImage with a source offset for a
streamed linear image (the XRGB8888 case), vkCmdCopyBufferToImage
with a buffer offset and row length for a staging buffer, and a word
offset in the RGB565 compute upload's UBO (rgb565_to_rgba8888.comp,
.inc regenerated). No host copy, no texture per frame. texture_optimal
is kept at the frame's size whenever it exists, since it is sampled
whenever it exists; a window-sized one left over from a window is
recreated for a staging texture and retired for a streamed one. A
frame inside the mapping that is not a window (another pitch) is
still copied out, and the cached frame is then retired, so a paused
redraw can never follow a parked mapping again.

The D3D12 driver's loan is a persistent buffer of its own, so it was
memory-safe, but it decided by a flag set when the loan was asked
for, not by what was pushed: a window pushed into it was copied as
the whole footprint into a window-sized texture, and a core that
asked and then pushed its own buffer, or a dupe, was shown the loan.
It now makes the same pointer test (d3d12_sw_fb_window) and copies
the window's box out of the loan's footprint; the flag is gone.

Three GFX_INSTRUMENT counters cover the path: frame textures
created, frames copied by the host, frames read out of a loan.

The threaded-video harness gets a lane for it: threaded video off on
the real driver, the harness core's cropped-window lend for 60
frames, then the menu opened over the paused core for longer than
any deferral window. On the old driver on lavapipe under ASan the
lane counts 59 grants and 0 frames read out of the loan, then the
paused redraw aborts with the use-after-free; on the new one 39 of
40 pushes are read out of the loan (the 41st is the core's own
oversize frame, copied as it should be), no host copy, 3 frame
textures over 60 frames, and the paused redraw runs through with
the validation layer silent. It runs on the release, ASan and TSan
legs of Linux-vulkan-validation.yml as they are.
2026-09-30 03:47:56 +02:00
LibretroAdmin 8261ec0550 explore: build the index in steps under the shared I/O window
menu_explore_build_list was one handler call: every playlist parsed,
every RDB walked, every category sorted - on the frame thread when
Threaded Tasks is off.  The build is now begin/step/end/abort over
three phases (playlists through the resumable playlist parser, RDB
cursor items, one sort per step), consulting a budget between work
items; build_list is begin + one unbudgeted step + end, so the two
cannot drift.  The explore init task steps it under the shared
per-frame I/O window and aborts cleanly on cancel.

Harness samples/menu/explore_build writes a collection of playlists
and databases, checks the index built one item per step hashes the
same as the one built in one go, and that the real task returns
unfinished across checks and installs the same index.
2026-09-30 03:43:17 +02:00
LibretroAdmin f890800f72 content: the load is a chain of main-thread tasks
Each stage of the staged load is a task of the frontend's queue with
RETRO_TASK_FLG_MAIN_THREAD: its handler runs one stage per check and
returns unfinished while the stage waits, its callback pushes the next
stage, and the last one runs the after-work.  The hand-stepped
task_content_load_check and its hook in runloop_iterate go; the
prefetch continuation starts the load from its own callback.

The close, now a handler, asks whether a save state task is inside
the core from inside the gather, where the unthreaded runner has
lifted every task off the queue and a finder sees none: it judged
nothing pending and unloaded the core under a save still writing.
content_save_state_in_progress is now a flag that transitions on the
main thread - set at the push, cleared in the callback - as the
load-state one already is.  The harness lane that closes content
over a save in flight is what found it.
2026-09-30 03:07:28 +02:00
libretroadmin 00e29fe967 Makefile.common: TLS follows networking; every platform makefile checked
Makefile.retrofw asked for HAVE_RETROSSL with no HAVE_NETWORKING:
Makefile.common set HAVE_SSL and the define anyway, retroarch.c called
ssl_socket_set_verify_mode() under HAVE_SSL, and the object holding
it is only built with networking, so the RetroFW link failed. A
makefile without networking now gets no TLS backend at all, and the
call site is gated on networking as its definition is.

tools/platform_makefile_check.sh takes every platform makefile built
on Makefile.common - RetroFW, the OpenDingux family, webOS - without
its toolchain: the graph is generated with stand-ins, the DEFINES it
hands the compiler are read back and checked for consistency (TLS,
SMB and NFS need networking, the clients and the keychain need the
crypto, HAVE_SSL needs an SSL object in the graph), and the frontend
units are compiled by the host compiler under exactly those defines
with implicit declarations as errors. On the old Makefile.retrofw it
fails on the gate; on all eight it passes now. The compile matrix
runs it.
2026-09-30 03:07:16 +02:00
LibretroAdmin 8bd7c775d4 content: the close waits for a state task from the frame loop
A save or load state task still inside the core holds the close:
the task calls the core's serialize, and the close unloads the
library it lives in.  CMD_EVENT_CORE_DEINIT and RARCH_CTL_MAIN_DEINIT
split into a begin half (options, unpause, RAM state, the auto-save),
a pending predicate and a finish half; the commands still wait
between the halves, and the staged load returns to the frame loop
between them, asking each frame, so the frames keep presenting
"Saving state" and core_run's content_closing guard keeps retro_run
out.  The harness core carries a 64 MiB state so a save spans
several checks; the lane closes content over one and counts the
frames presented while the close waits.
2026-09-30 02:46:53 +02:00
libretroadmin f85123b0f5 consoles: the cleanroom crypto and TLS client on 3DS, Vita, Switch and OpenDingux
The last builds carrying another TLS library: the 3DS and Vita
makefiles compiled the bundled mbedtls, the Switch linked devkitPro's,
and the OpenDingux family had the bundled mbedtls (dingux) or a
HAVE_OPENSSL that linked a library no backend used (rg350, retrofw).
All six now select HAVE_CRYPTO, the keychain and HAVE_RETROSSL, as the
desktop, mobile and Windows builds do: the TLS 1.2/1.3 client with
certificate verification and the encrypted keychain, from sources the
tree already has, with nothing to link. The crypto's random source
already had a path for each of them (PS_GenerateRandomBytes,
sceKernelGetRandomNumber, randomGet). The 3DS makefile's note that TLS
could not go through griffin no longer holds, the unity lane proving
it, and the mbedtls include path goes with it.

The compile matrix compiles the crypto and keychain in the 3DS and
Vita shapes and the whole TLS client in the Switch shape, with the SDK
stand-ins the random sources need added to the stubs, and the whole
set with a MIPS cross compiler for the OpenDingux family; the matrix
job installs that compiler. The Vita, 3DS, Switch and RetroFW
workflows build the real things.
2026-09-30 02:38:53 +02:00
libretroadmin 8f602a2e7b crypto: AES-CCM length check as 64 bits; the crypto suite on MIPS
aes_ccm_core checked that the message length fits the L octets the
nonce leaves with len >> (8 * L), and for the 11-octet nonce SMB 3.0.2
uses L is 4: on a 32-bit size_t that is a shift by the word width,
undefined, and on MIPS it comes back unshifted, so every sealed
SMB 3.0.2 message was refused there. The same holds for 32-bit x86
and, by the letter of the language, every 32-bit target. The check is
done as 64 bits.

The crypto suite now also runs under qemu-mipsel: a 32-bit,
strict-alignment target, the one such a Linux host can run, standing
in for the 32-bit ARM and x86 builds. It fails on the old line and
passes on the new; the libretro-common tests job installs the MIPS
cross compiler for it.
2026-09-30 02:38:49 +02:00
LibretroAdmin af2997c9d2 task_queue: RETRO_TASK_FLG_MAIN_THREAD runs a handler on the checking thread
A task pushed with the flag has its handler run by task_queue_check()
- the main thread - under every runner: the threaded worker and the
GCD dispatch pass such tasks by, and the gather runs the due ones once
each within the handler budget, settling them as the worker settles
its own.  Work that must stay on the main thread (calls into the
core, the drivers, the menu) gets a task's lifetime: pushed, found,
cancelled, retired through callback and cleanup.

Sample task_queue_main_thread_test covers both runners: where the
handler runs, one call per check while unfinished, a future 'when',
task_queue_wait(), and plain tasks interleaved with main-thread ones.
tools/platform_stubs/gcd carries a dispatch.h stand-in so the HAVE_GCD
arm syntax-checks with clang -fblocks off Apple.
2026-09-30 02:23:13 +02:00
LibretroAdmin 1b5a5a84ff tools: ci_configure_link_check.sh builds every CI configure set
Reads each distinct ./configure flag set out of .github/workflows,
configures it in a worktree of HEAD and builds: --objects compiles the
frontend units and reports a symbol they call that the default build
defines but this set compiled out (a function inside #ifdef HAVE_MENU
called from runloop.c, the --disable-menu lanes' failure), the
default mode links the whole program.  Sets this host cannot
configure are skipped, cross-host sets listed.
2026-09-30 02:07:06 +02:00
LibretroAdmin cf4ae7e14b content: a load keeps the task queue and applies its settings
The queue was torn down and rebuilt at every load, which joined the
worker thread from a menu action.  retroarch_init_task_queue now
splits: startup builds the queue, and a load onto a running session
calls retroarch_task_queue_configure, which applies Threaded Tasks
(the queue swaps at the next check, between frames), the fast-cores
preference, the slow-handler probe and the budgets.  Every task in
flight, and the worker, survive the load; the load can become a task
of that queue.

Harness lane: under the threaded queue, a thread-local mark set by a
task before a reload is still on the worker after it, and a task in
flight across the reload retires afterwards.
2026-09-30 02:07:02 +02:00
libretroadmin 61e1817b3f lrc_hash: name the SHA-512 sigma macros after the algorithm
BS0 and BS1 are baud-rate constants in the termios headers, which the
Android unity build has in scope by the time lrc_hash.c is reached,
so clang reported the SHA-512 sigma macros as redefinitions. They are
SHA512_BSIG0/BSIG1/SSIG0/SSIG1 now, the names RFC 6234 gives them;
the hash and crypto suites pass unchanged.
2026-09-30 01:50:48 +02:00
LibretroAdmin 613da2ea55 content: task_content_load_check is built without the menu
It sat inside the menu-only block, so a menu-less build's frame loop
called a function that was compiled out.
2026-09-30 01:37:40 +02:00
libretroadmin e8127b7245 griffin: prefix the SMB client's octet helpers; a full-unity lane
The Android and Apple builds failed as soon as they carried the SMB
client: their griffin unit also holds rvorbis.c and iec61937.c, whose
static get32 and put16 collide with net_smb2.c's. The helpers are
now smb_get16/32/64 and smb_put16/32/64, crypto.c's aes_ccm_crypt
becomes aes_ccm_core so it cannot meet libsmb2's either, and the
NFS backend's window size is cast to the uint32_t the client takes,
which Xcode warned about.

The host griffin lanes never saw the clash because none of them
carried a codec beside the network stack. The compile matrix gains a
full-unity lane, griffin with every codec, image reader and subsystem
a host can hold beside the whole network stack, which is the shape
the Android, Apple, webOS and MSVC targets ship; on the old file it
fails on exactly these two names.
2026-09-30 01:32:39 +02:00
libretroadmin 3cbe3fe57f msvc: the cleanroom network stack in the 2019, 2022 and UWP projects
The MinGW builds have carried the stack since configure took it up,
but the MSVC 2019 and 2022 projects, which the x64 and ARM64 jobs
build, had networking with no TLS or SMB client at all, and the UWP
project had libsmb2 and no TLS. All three are griffin builds, so the
change is their preprocessor definitions: HAVE_CRYPTO, the keychain,
HAVE_RETROSSL, HAVE_RETROSMB and HAVE_RETRONFS on every configuration
line that has HAVE_NETWORKING, giving those builds the TLS 1.2/1.3
client with verification, the encrypted keychain, the SMB2/3 client
with signing, sealing, Kerberos and the read-ahead window, and
nfs://. UWP loses the libsmb2 configuration defines and include
directories with it and links bcrypt for the random source its
WinRT path draws on.

The stack is C89 and MSVC-clean by design; every translation unit
compiles at the _WIN32_WINNT=0x0400 floor with MinGW, and the Wine
lanes run the Windows binaries. The projects themselves are the
MSVC and Windows ARM64 workflows' to build.
2026-09-30 01:19:51 +02:00
libretroadmin 7265f686b4 apple, webos: the cleanroom network stack in place of mbedtls and libsmb2
The Apple targets still had griffin pull in the bundled mbedtls for
TLS, and the webOS build both mbedtls and libsmb2. BaseConfig.xcconfig
and Makefile.webos now select HAVE_CRYPTO, the keychain, HAVE_RETROSSL
and HAVE_RETROSMB as the desktop and Android builds do, and add the
NFSv3/v4 client, so macOS, iOS, tvOS and webOS get the same TLS
1.2/1.3 client with verification, the encrypted keychain, the SMB2/3
client with signing, sealing, Kerberos and the read-ahead window, and
nfs://; neither bundled library is compiled in any more. The Apple
MBEDTLS_BUILTIN_FLAG stays a variable a deps.xcconfig may still set:
with HAVE_RETROSSL defined griffin takes the built-in client either
way. The keychain's Darwin path resolves gethostuuid at run time
already, so the old SDK floors hold.

The nine objects of the stack compile through Makefile.webos with its
cortex-a9 softfp ARCHFLAGS on an ARM cross compiler with neither
warning nor error; the Xcode build is the Apple workflows' to run.
2026-09-30 01:12:22 +02:00
libretroadmin db9e44da97 android: the cleanroom network stack in place of mbedtls and libsmb2
The APK still had griffin pull in the bundled mbedtls for TLS and
libsmb2 for SMB. Android.mk now sets HAVE_CRYPTO, HAVE_KEYCHAIN,
HAVE_RETROSSL and HAVE_RETROSMB as the desktop builds do, so the APK
gets the same TLS 1.2/1.3 client with certificate verification, the
encrypted keychain, and the SMB2/3 client with signing, sealing,
Kerberos and the read-ahead window, and neither bundled library is
compiled in any more; the libsmb2 configuration defines and include
paths go with it. The mbedtls path stays selectable by turning
HAVE_RETROSSL off.

The compile matrix gains a lane per translation unit of the stack
compiled by clang for aarch64 with the cross headers standing in for
the NDK's: the same compiler family and target the APK is built
with, which is where the AES/PMULL path takes its +crypto target
attribute and which the gcc lanes never exercised. The matrix job
installs clang and the aarch64 cross toolchain for it.
2026-09-30 01:08:28 +02:00
LibretroAdmin 7e13753f73 content: carry the core's hardware-render request across the driver stage
A hardware core makes its SET_HW_RENDER request during the core
stage, while the previous session's drivers are still up.  Freeing
those drivers ran video_driver_free_hw_context on that request:
the new core's context_destroy before any context_reset, and the
request zeroed, so drivers_init built no context and the core ran
with none.  The driver stage now takes the request out before the
teardown and puts it back for drivers_init, the way a reinit
carries a running core's; the teardown moves into
retroarch_main_init_drivers with it.  Context flags set in that
window (glcore's core-context flag) defer to the next context
instead of landing on the one being freed.

The harness core makes such a request at load; a lane checks it
survives the rebuild with nothing destroyed, and that flags set
mid-load defer even on a context that takes them.
2026-09-30 01:01:00 +02:00
libretroadmin b5de27786d android: build the NFS client and the nfs:// backend
HAVE_RETRONFS in the Android makefile: the built-in NFSv3/v4 client
and its backend, which griffin already carries under HAVE_NFSCLIENT,
need nothing but sockets, so the APK gets nfs:// beside the smb:// it
had. The compile matrix compiles the client, the backend and the
prefetcher through the Android platform stubs.
2026-09-30 00:59:13 +02:00
libretroadmin 86bf1571f6 vfs: background prefetcher for the smb:// and nfs:// backends
The read-ahead window still stalls the reading thread once per
window, for a round trip and a window's worth of transfer, which on a
slow link is a visible pause every megabyte. vfs_prefetch.c is a
prefetcher a stream opened read-only reads through: a thread of its
own fetches aligned windows ahead of the reader on a connection of
its own (a second pool slot, or a private one), so a run of
sequential reads is served from memory and the reader never waits
for the network. The reader's side takes no lock: slots move
EMPTY -> FILLING -> READY under the thread and READY -> EMPTY under
the reader alone, so every write the thread makes to a slot is
ordered after the reader's CAS and every read the reader makes after
the thread's release; a seek out of the fetched range retargets the
thread by generation, a miss retargets it when it has fallen behind
rather than let it fetch what the reader pulls itself, and the thread
parks on a retro_eventcount when it has nothing to do. Windows are
the client's read-ahead size, two of them. On a miss the stream reads
the network as before, so without HAVE_THREADS, where the prefetcher
is stubs, nothing changes.

The nfs:// close now sends the NFSv4 CLOSE on a connection, which the
connectionless close skipped. The one object is built whenever a
network backend is, in griffin and the sample Makefiles too. A stress
test of random reads and seeks with every octet checked runs clean
under TSan and the address sanitizers, and the three server matrices
pass under TSan with the prefetcher active in the VFS lanes.
2026-09-30 00:59:09 +02:00
LibretroAdmin 587f86ea89 samples/tasks: content_load harness for the staged load
Links the shipping objects and drives the menu's load entry point on
a reload of the harness core, hooking the driver's frame() to count
what is presented.  Lanes: a frame drawn with the old core down and
another on the old drivers once the new core is up (a synchronous
load presents nothing between and fails), a second request refused
while one is in flight, a reinit deferred to the load's own driver
stage, the dummy-core fallback, and Close Content's reload landing
after the dummy session rather than in the middle of it.

Run by hand against a non-Qt build:
  ./configure --disable-qt && make
  make -C samples/tasks/content_load check
2026-09-30 00:42:52 +02:00
LibretroAdmin 2db2e86ba7 content: load content as a staged job the frame loop advances
The menu load ran the whole close-core/init-core/init-drivers cycle
inside one runloop_iterate, so the window went dark - or dropped to
the compositor on webOS - for the length of the read.  content_load
is now a job of three stages the frame loop steps through one per
frame: the old core closes with the drivers kept up, the new core
comes up behind them, then one driver reinit follows.  Between stages
the loop presents the cached frame under the menu, so the window
keeps drawing.

retroarch_main_init splits into a core phase and a driver phase for
this: the drivers are found in the driver phase, after the previous
session's are freed, so both sessions never hold a driver at once.
Core deinit keeps the drivers while switching, a reinit the core asks
for mid-load defers to the driver stage, and the pending menu actions
(config replace, close, core reload) wait for the frame after.
Startup and the CLI still load in one go, before any frame runs.
2026-09-30 00:42:48 +02:00
LibretroAdmin 4c61f9fede samples: frontend-overhead harness - heap on the frame path, and microseconds per frame
The threaded-video harness already counts heap calls over the frame
path, but its core pushes no audio, so the sample-batch callback -
where the fold and remap staging used to grow on the callback - was
never under that gate. Nothing in the tree measures what the frame
loop itself costs, either; the frame-path audit had to build that by
hand to learn the answer (about 5 us per iterate with an empty core,
on the null drivers).

samples/runloop/frontend_overhead links the shipping release objects
with main() replaced, the way the threaded-video harness does, boots
on the null drivers, and loads a core whose retro_run() does the
least a real one does: one 320x240 RGB565 frame, one 735-frame stereo
batch, one input poll and read. Two lanes:

  - heap: zero general-purpose heap calls over 300 frames of iterate
    + video frame + audio batch, counted by glibc interposition. A
    single malloc per frame in the core fails it with the count
    (checked: 300 over 300).

  - cost: microseconds per frame under fast-forward, engaged the way
    the hotkey does it so the pacer sees real facts. Printed so a
    trend is visible in the log; tripwire at 1000 us, loose on
    purpose, to catch a blocking call or a sleep landing on the frame
    path rather than to tune. 3-5 us here.

Linux-frontend-overhead.yml runs it on a default non-Qt release build
- no sanitizer, since one would replace malloc and be counted.
2026-09-29 23:49:34 +02:00
libretroadmin 841cbde3f4 local_ad_dc_test.sh: the skip and failure reports survive set -e
The script runs under set -e, and the oracle was called as a bare
statement: when Samba's own client failed on the runner, the script
ended on that line, exit 1 and not a word, instead of skipping with
the report. The oracle is now called in an if, and the report is
printed by a helper that turns errexit off, so a log that does not
exist yet cannot cut it short either. Both paths were forced locally
and print what they should.
2026-09-29 23:39:50 +02:00
LibretroAdmin d1a30ecab1 runloop: cheevos on a frame_work bit, presence on the 10 Hz cadence, setter contract
Three follow-ups on the product mask.

Cheevos was the product left on a settings bool: both rcheevos_test()
sites in the iterate read settings->bools.cheevos_enable and called
into cheevos.c every frame, whether or not anything was attached.
Put it on RUNLOOP_WORK_CHEEVOS with an honest lifetime: rcheevos_load()
raises it once it has passed the enable / core-support / account gates
and committed to loading content, rcheevos_unload() drops it. Between
those the tick runs exactly as before (the bg-thread mailbox, then
rc_client_do_frame or rc_client_idle). One visible change: flipping
cheevos_enable off mid-content no longer silently stops the tick,
which previously left a game attached but untested until the next
load; the attach/detach now follows content, as rcheevos already
assumes.

presence_update(PRESENCE_GAME) ran every frame while the bit was on.
"In a game" is a state, not a per-frame event, and its sinks are
Discord (whose pump is already 10 Hz) and Steam. Add
presence_poll(presence, now_us), a 100 ms throttle on the frame's
existing clock, and call that from the iterate. No edge is lost:
each sink compares against its own last status, so a change between
polls is written at the next one.

Document the threading contract on runloop_frame_work_set(): frame_work
is a plain word the iterate reads without an acquire, so every setter
must be on the main thread. All current callers are (init/deinit,
commands, setting handlers); a worker must post a command instead.
2026-09-29 23:34:55 +02:00
libretroadmin 265f7b1f1b smb, nfs: read-ahead window and pipelined reads
A game reading a disc image in small pieces paid one network round
trip per read, and over a real link that is the stutter reported
against the SMB backend. Both clients now keep a read-ahead window
per open file: a read smaller than the window is served from it, and
a miss refills the window from the current offset with pipelined
READs, up to eight in flight (SMB within the credits the server has
granted, NFS by RPC xid, matched as they come back in any order), so
a run of small sequential reads costs one round trip per window and
the fill runs near line rate. A short or empty reply ends the
contiguous run there whatever later chunks answered; reads at least
a window long go straight to the caller's buffer; a write, truncate
or seek outside the window invalidates it; the window is allocated
on the first small read and freed with the file. The named pipe the
share enumeration talks to keeps single-shot reads, since a pipe
holds extra reads pending. On the loopback, 2 KiB reads of a 256 MiB
file went from 37 to 347 MiB/s; with latency the gap is the ratio of
window to read size.

The window defaults to 1 MiB and is a setting per client -
smb_client_readahead and nfs_readahead, KiB, 64 to 16384, under the
SMB and NFS client settings - since it is that much memory per open
file. The nfs_test and smb_test samples read their file through a
4 KiB window in small pieces, across its edges, after seeks in and
out of it and at the end, so every server in the matrices exercises
it.
2026-09-29 23:26:49 +02:00
libretroadmin 042ddff59d fuzz: fuzz_smb links net_krb5.c; binaries depend on their sources
net_smb2.c calls into net_krb5.c since the Kerberos session setup,
and the SMB fuzzer's link line did not carry it. The four fuzz
binaries also depended only on net_fuzz.c, so a change in any client
they link left a stale binary in place and a local run passed on it;
they now depend on everything they link.
2026-09-29 23:00:30 +02:00
LibretroAdmin 55e82c3898 extra_capacity: the record lane carves its remap staging the way init does
The push no longer grows the region on the callback, so the lane
checks that it never allocates and that a state without staging drops
the audio while the int16 same-layout bypass still records.
2026-09-29 22:48:48 +02:00
LibretroAdmin f21180fe42 retro_eventcount: include the tree's own coreinit headers on the Wii U
Makefile.wiiu compiles against wiiu/include, not wut, so the event
and time headers live under wiiu/os and the tick conversion is spelled
OSMicroseconds there. tools/wiiu_syntax_check.sh compiles the rthreads
units with that build's include directories and defines.
2026-09-29 22:48:44 +02:00
libretroadmin ff175e0075 local_ad_dc_test.sh: Samba's own client as the oracle, logs on failure
The runner's domain controller provisioned but refused the ticket at
SESSION_SETUP, which passes here; the difference is in the runner's
environment, not visible from one status line. Before the built-in
client is tried, Samba's own smbclient now logs in to the DC with a
ticket from kinit: when it cannot, the DC is not usable there and
the lane skips, printing smbclient's and the DC's log tails; when it
can and the built-in client cannot, that is a real failure, and the
DC's logs are printed with it. The daemon runs with the krb5.conf
its provisioning wrote, and the workflow installs smbclient.
2026-09-29 22:25:55 +02:00
LibretroAdmin 1e77b228a6 retro_eventcount: park on the address arbiter on the 3DS
The 3DS arbiter is the futex shape with a less-than comparison: it
waits while the word is strictly below the value given.  The epoch only
climbs, so waiting while it is below key + 1 is waiting while it still
reads key, and a notify signals every waiter on the word.  INT_MAX has
no successor to compare against; a wait registered there is answered
as a spurious wake, once per wrap of the counter.  The unbounded wait
is a long bound re-armed while the epoch still reads key.

The test harness gains a CTR=1 build of the backend on the host, the
two arbiter wrappers stood in for over futex, and a lane at the epoch's
wrap: a wait registered at INT_MAX comes back, and one registered just
past the wrap is still woken.
2026-09-29 22:17:36 +02:00
LibretroAdmin bdc073cbb9 retro_eventcount: park on a pooled auto-reset event on the Wii U
The pooled backend with an OSEvent in auto-reset mode in place of the
semaphore: a signal that arrives before the wait sets the event and the
wait then returns at once, which is the counted property the protocol
needs, since it signals at most once per park.  The bound goes through
OSWaitEventWithTimeout in timer ticks.
2026-09-29 21:54:25 +02:00
libretroadmin b9ded403e3 CI: the AD DC lane installs samba-vfs-modules
The domain controller's provisioning failed on the runner with
vfs_init_custom failed for acl_xattr: the acl_xattr VFS module its
file server needs is in samba-vfs-modules, which samba only
recommends and --no-install-recommends leaves out. Reproduced by
removing the package. The login wait in local_ad_dc_test.sh also
retries a refused session setup for a while, since the KDC and file
server come up in their own time after the user exists.
2026-09-29 21:54:08 +02:00
LibretroAdmin e69947d832 menu: initialise walk_status in menu_displaylist_parse_playlists
Since the horizontal (driver-owned) list enumerates synchronously,
walk_status is only assigned on the interactive-playlists branch.
Initialise it to MENU_DIRWALK_FAILED so the variable has a defined
value on every path, matching the file browser site.
2026-09-29 21:47:11 +02:00
LibretroAdmin 5b5ee25450 retro_eventcount: park on a pooled lv2 semaphore on the PS3
The Vita's pooled semaphore backend, on the PS3's lv2 semaphore: both
SDKs' spellings, as rthreads.c has them for its mutex and condition
variable, and an lv2 wait's microseconds with zero standing for no
timeout.  Any failure of the wait is answered as a timeout, so the
block comes off the list before its frame goes.
2026-09-29 21:46:18 +02:00
libretroadmin 6bbde7b981 smb: Kerberos realm and KDC settings for the smb:// backend
smb_client_realm and smb_client_kdc join the SMB Client settings
(descriptor rows next to the authentication mode, configuration rows
kept literal as the cloud sync strings are). They reach the backend
through smb_settings and the connection key, and the compat shim's
Kerberos mode is now real: smb2_set_kerberos() hands the realm and
KDC to the client, and a KRB5 request needs a realm and succeeds
only when the session came from a ticket, so a pool trying Kerberos
first records the truth and its NTLMSSP attempt still runs, while an
NTLMSSP request never touches the KDC. Share enumeration takes the
same path. A libsmb2 build reads its realm from krb5.conf as before,
so the setter is HAVE_RETROSMB only.
2026-09-29 21:45:35 +02:00
keycross 4eb993442b Fix horizontal playlist enumeration at menu init 2026-09-29 21:47:47 +02:00
LibretroAdmin 864efd0d52 discrete_multichannel harness: carve the staging regions as init does
The fold staging and the recorder remap staging are carved out of the
arenas at init now, at fixed sizes, and the batch callback no longer
grows them.  The harness stands the state up by hand, as init does it,
and gave the state neither, so every fold answered as having no room
and the lane counted 165 failures.  Both stand-ups carve the two as
init does, and the two checks that watched them grow now watch them
stay.
2026-09-29 21:35:07 +02:00
libretroadmin 3b4a816719 menu: NFS Network Settings opens; SMB and NFS entries only under Network
The NFS Network Settings entry opened an empty "null" browser: none
of the NFS client labels had a row in msg_hash_lbl.h, so the label
its deferred list is pushed with resolved to "null" and the push
found no list. Add the rows and their identifier literals, as the
SMB ones have.

Both entries leave the Quick Menu: they are global settings that
already sit under Settings > Network, and a per-game menu is the
wrong place for a client configuration.

samples/menu/network_settings links the shipping objects with only
main() replaced, resolves the SMB and NFS labels, opens Settings >
Network and both screens through the real dispatch (this reproduced
the empty screen before the fix), and checks the Quick Menu no longer
carries the entries. The menu-harness workflow runs it and keeps
networking enabled in its configure so the screens are compiled in.
2026-09-29 21:20:51 +02:00
libretroadmin 88645f57a4 local_ad_dc_test.sh: provision on a filesystem that takes POSIX ACLs
Provisioning the domain controller sets POSIX ACLs on SYSVOL and
failed in setsysvolacl on the runner, where the temp directory does
not take them. The script now probes for ACL support with setfacl
and provisions in the first place that has it - the temp directory,
the repository's own filesystem, then /var/tmp - and skips with a
clear message when none does. A provisioning failure prints the
log's tail so the reason is visible. The workflow installs acl for
the probe.
2026-09-29 21:12:25 +02:00
LibretroAdmin 7c7ed62350 retro_eventcount: park on a pooled kernel semaphore on the Vita
The semaphore backend kept a semaphore per thread in a thread key,
which the console ports' threads do not have.  The Vita takes the same
backend with the semaphores drawn from a pool instead: a lock-free
stack of kernel semaphores, one taken for the length of a park and put
back on return, which the park protocol allows because a park never
returns with a signal still counted on it.  A pool that has run out
answers a park as a spurious wake after a yield.  The waiter list's
lock bit is now spun for a bounded count before yielding, so a holder
that is not running gets back on when the threads outnumber the cores.

The test harness gains a SEMPOOL=1 build of the pooled variant over
POSIX semaphores, and a crowd lane: more threads than the pool has
semaphores, all parking with a bound against a moving notifier, then a
concurrent probe on the semaphores handed back and forth, which must
expire clean on any semaphore backend.
2026-09-29 21:04:37 +02:00
libretroadmin acef386074 net_smb2: Kerberos authentication through SPNEGO
rsmb_set_kerberos(realm, kdc, port) makes the session setup obtain a
TGT and a cifs/<server> ticket through net_krb5 and send the RFC 4121
context token in a NegTokenInit with the Kerberos mechanism; the
server's NegTokenResp carries its AP-REP, which is verified before the
first sixteen octets of the context key become the SMB session key
(MS-SMB2 3.2.5.3.1) that the per-dialect signing and sealing keys are
derived from, now in one rsmb_derive_session_keys. An unreachable KDC
or a refused ticket falls back to NTLMSSP as before, and when that
fails too the error names both. The KDC defaults to the server, which
is right for a domain controller; service principals are named by
host, so a server given by address goes the NTLMSSP way.
rsmb_used_kerberos() reports which one the session came from.

smb_test takes the realm, KDC and port from SMB_KRB_REALM, SMB_KRB_KDC
and SMB_KRB_PORT, with SMB_KRB_REQUIRE failing a run that did not get
in by ticket. local_ad_dc_test.sh provisions a Samba Active Directory
domain controller on the loopback with NTLM disabled and mandatory
signing, creates a user, and runs the login, a wrong password, an
unknown user, and NTLMSSP alone (refused, so the login was the
ticket); the standalone matrix gains a lane where Kerberos is
configured but no KDC answers, so the fallback is exercised. The
network-clients workflow runs the DC matrix natively, under the
sanitizers and with the Windows tools under Wine, installing the AD DC
packages that samba only recommends.
2026-09-29 20:52:28 +02:00
LibretroAdmin d06baf07af savestate: time the automatic interval from the frame's clock, not time() per frame
With savestate_automatic_interval set, content_save_state_automatic()
called time(NULL) every frame to see whether the interval had passed.
The iterate already has the frame's cpu_features_get_time_usec()
reading in hand; pass that in and keep the last-save stamp in the
same units, so the check is a subtract and compare with no clock call
of its own.
2026-09-29 20:48:27 +02:00
LibretroAdmin 4239619d08 audio: carve the recorder remap staging out of the int16 arena at init
audio_driver_record_push() grew record_remap with realloc() on the
core's sample-batch callback the first time a recording needed a
layout or format remap - the same grow-on-the-callback the fold
buffer had before it was moved into the float arena. Recording is
exactly when a hitch on the audio path is visible in the capture.

Both halves are bounded: the slice is at most AUDIO_CHUNK_SIZE_
NONBLOCKING >> 1 frames, and neither a core's layout nor a recorder's
can declare more than AUDIO_RECORD_REMAP_CHANNELS channels. Carve two
halves of that size out of arena_int16 in audio_driver_init_internal()
next to the rewind region, drop the separate free, and make the size
test in the push a bound check that skips the batch rather than a
grow.
2026-09-29 20:48:23 +02:00
LibretroAdmin cfa7a7f6e0 wdmks: a ring of two fragments, and the geometry run as a regression lane
The refill moves the frontend ring into the loop once per notification,
and the ring was one fragment: supply exactly what a fragment plays, so
a pass that came late left a deficit nothing repaid and every fourth
pass was short for good.  The ring is two fragments now, so a pass can
move the fragment and the deficit.  The margin a resync leaves is
capped at half the loop rather than a quarter, which cut it under the
FIFO on a loop of two small fragments.  The loop's sizing is in one
function, wdmks_rt_geometry, so the harness runs the driver's numbers.

samples/audio/wdmks_loop gains the lane that runs them: for a latency
setting, the loop and ring the driver would make, the position moving
half a loop per notification, the refill once per notification, the
consumer feeding chunks of half the buffer it is told, one pass late by
a fragment and a half.  The loop must never run dry on time, cost at
most one short pull for the late pass, and heal.  It fails on each
geometry that shipped and crackled: a loop four times the setting, a
ring of one fragment, a margin under the FIFO.
2026-09-29 20:37:20 +02:00
LibretroAdmin c2d5b30078 video: vectorisable XRGB2101010 narrow, scratch sized at init instead of on the frame
video_driver_convert_xrgb2101010() is the fallback for a driver that
cannot present a 10-bit source surface, and it ran on the core thread
right before present with two costs it did not need. It grew its
scratch with realloc() the first time a frame arrived - a heap call on
the frame path - and its inner loop extracted each channel to its own
10-bit value before shifting it back into place, a form the compiler
did not vectorise.

Size the scratch in video_driver_init_internal() from the core's
declared max geometry, so the grow in the convert is only reached by
a core that delivers more than it declared. Rewrite the narrow as one
shift and one mask per channel directly into the output position;
output is bit-identical (checked against the old loop over a full
frame of random pixels) and at -O3 the compiler vectorises it. On a
2.1 GHz Xeon at 1920x1080: 736 us -> 631 us, the same as hand-written
SSE2 (604 us) - the loop is memory-bound past this point. For
comparison the 0RGB1555 scaler path, which the audit also flagged, is
7 us at 320x240 and 31 us at 640x480 on the same box, which is why it
is left alone.
2026-09-29 20:35:54 +02:00
LibretroAdmin b04b1a7afe video: format the FPS and memory status text on their update ticks, not every frame
With fps_show on, video_driver_frame() ran snprintf("%6.2f") on the
core thread every frame to print a value that only changes once per
fps_update_interval; with memory_show on it did the same for two
figures that only move once per memory_update_interval. That is
floating-point formatting on the frame path, right before present,
for a string that is identical to the previous frame's almost every
time.

Keep each piece as a static string: the FPS text is reformatted when
last_fps actually changes (a float compare per frame), the memory
text on the interval tick that already refreshes the figures. The
per-frame work is a strlcpy of a short cached string into
status_text. The frame counter is left as it was, since it changes
every frame when shown, and the time text was already cached.
2026-09-29 20:27:18 +02:00
LibretroAdmin 9700decb76 wdmks: two fragments of the held amount, refilled on the notification
The probe that slept 40 ms at init to measure how the position moves
is gone: a driver does not learn about a device by blocking on a
timer, and the refill did not need to pace on one either.  The loop is
two fragments, each the latency setting's worth: one is held ahead of
the hardware, the driver's two notifications a loop land once a
fragment as the hardware leaves it, and the refill wakes on that and
fills what it left.  The hardware's clock paces; the timer is a
watchdog.  The position can be stale by no more than a fragment by
construction, and a pass late by up to a fragment is still inside the
lap, which is what the loop the size of the setting could not give.
2026-09-29 20:12:46 +02:00
LibretroAdmin e39e641802 runloop: put cheats, presence, camera and BSV behind frame_work bits
The post-core_run product block called into four translation units
every frame to find out nothing needed doing: cheat_manager_apply_
retro_cheats() loads a cold global to see there is no cheat list,
presence_update() calls discord_update() and steam_update_presence()
which each compare a status word and return, the camera poll reads
settings_t for camera_allow before the caps check, and
bsv_movie_next_frame() pulled two settings through config_get_ptr()
before checking whether a movie handle exists at all.

Give each an up/down owner and a bit on runloop_state_t::frame_work,
raised and dropped by the product itself through
runloop_frame_work_set():

  RUNLOOP_WORK_CHEATS   - cheat_manager_new/realloc raise it when a
                          list is allocated, cheat_manager_free drops it
  RUNLOOP_WORK_PRESENCE - raised with Discord init and with Steam
                          (mist) init; dropped with whichever of the
                          two is the last compiled-in sink
  RUNLOOP_WORK_CAMERA   - driver_camera_start raises it when the core's
                          start succeeds, driver_camera_stop and the
                          driver teardown drop it. The remaining
                          camera_allow/caps/driver checks stay behind
                          the bit; a camera the core registered but
                          never started is no longer polled.

BSV needs no bit: the movie handles live on input_st, which the frame
already has in cache, so the iterate tests the pointers directly and
bsv_movie_next_frame() checks its handle before touching settings.
2026-09-29 20:11:58 +02:00
LibretroAdmin 34d87d5574 wdmks: hold at least two position steps ahead of a coarse position
A pin with no position register answers PlayOffset from its driver,
and a driver that reports from its fragment interrupts moves it a
fragment at a time - on a loop of two fragments, half the loop.  Audio
held ahead of a position stale by that much is audio the hardware may
already have passed: it plays the previous lap's bytes there, and the
count, which trusts the position, says nothing.  The step is measured
once the pin runs, 40 ms at 200 us, and what is held ahead is floored
at the margin and two steps before the frontend is told what that is.
Where the position moves finely nothing changes.
2026-09-29 20:09:29 +02:00
LibretroAdmin 3df357f12f runloop: gate the Discord pump on a frame-work bit, not a call into discord.c
Even throttled, the iterate reached the Discord pump by calling into
discord.c and loading a cold global to learn that Discord was not in
use. A product that is compiled in but idle should cost the frame one
bit test on a word the loop already has in cache, not a cross-TU call.

Add runloop_state_t::frame_work, an unsigned in the hot front of the
struct next to pace, with one bit per optional product that hooks the
iterate. Discord is the first: retroarch_main_init() raises
RUNLOOP_WORK_DISCORD when CMD_EVENT_DISCORD_INIT succeeds (which it
does not when discord_enable is off) and retroarch_main_quit() drops
it with inited. The iterate tests the bit and only then calls
discord_poll(). The other per-frame products (presence, cheats, BSV,
camera) can move behind the same word one at a time.
2026-09-29 20:07:36 +02:00
LibretroAdmin 4eff24c4e5 wdmks: pace the refill on the timer where the loop outsizes what is held
The notification event fires twice a loop, and the refill waits on it
where the pin gives one.  With the loop decoupled from the latency
setting - four times it - a notification comes every two settings'
worth of audio, and a refill that waits for it has run the held audio
out before it wakes: constant crackle on any pin with an event.  The
event is not taken when the loop is larger than what is held; the
timer path, whose cadence is sized from the held amount, paces instead.

samples/audio/wdmks_loop checks the choice.
2026-09-29 20:05:25 +02:00
LibretroAdmin 03cbe1796f discord: pump the RPC connection at 10 Hz from the runloop, not every frame
runloop_iterate() called Discord_RunCallbacks() and
Discord_UpdateConnection() at the top of every visible frame whenever
Discord was inited. UpdateConnection does a non-blocking read on the
RPC pipe (and a clock read plus reconnect backoff check while it is
not open) - a syscall on the emulation thread, every frame, for
callbacks that arrive a handful of times per session.

Add discord_poll(now_us), which runs both at most every 100 ms using
the frame's existing timestamp, and call that from the iterate
instead. Presence changes are queued by discord_update() and flushed
by the next pump, so nothing is lost; join/spectate callbacks arrive
within 100 ms, which is far inside what Discord itself tolerates. The
shutdown path in retroarch.c still calls Discord_UpdateConnection()
directly so the final ClearPresence goes out unthrottled.
2026-09-29 20:04:22 +02:00
LibretroAdmin a4372343b6 wdmks: hold the latency setting ahead of the hardware in a larger loop
The WaveRT loop was the size of the latency setting, so a 2 ms setting
made a 2 ms loop.  Two things went wrong with that.  A pass of the
refill thread 2.5 ms late was a lap the position could not be followed
across, so the whole loop was cleared and the cursor resynced, an
audible discontinuity for half a millisecond of missed audio.  And the
margin a resync leaves for the codec's FIFO was cut to a quarter of the
loop - 24 frames against a FIFO of 46 - so every resync, the start
included, wrote its first frames into bytes the hardware had already
fetched.

The loop is now four times the setting and 8 ms at the least, and the
setting is what is held ahead of the hardware beyond that margin: the
frontend's chunking, the ring it writes and the latency it is told are
all the setting's worth, a late pass has laps to spare, and the margin
is the FIFO's.

samples/audio/wdmks_loop gains the lane: a short setting on a large
loop fills the margin and the setting's worth and no more, and a pass
three settings late is an overrun on the held audio, not a lost lap.
2026-09-29 20:00:00 +02:00
LibretroAdmin 10c3cc5c58 runahead: clamp the frame count to what the measured step cost fits in the frame
Runahead is N + 1 core steps per visible frame plus a serialize and a
deserialize, and the serialize cost is the core's state size, not
something the frontend controls. For a 1-4 MB state that is nothing;
for a core whose state is tens of MB, or one that runs near the frame
budget on its own, N frames of runahead no longer fit and the result is
dropped frames and stretched audio - worse latency than not running
ahead at all. There was no gate: the requested count ran every frame
regardless of what it cost.

Measure the cost of one core step every frame (the whole of
runahead_run() divided by the steps it made, so the save and load are
folded in), IIR-average it (1/8), and clamp the frame count to what fits
in 70% of the core's frame period. The count climbs back only once the
request fits within 60%, so a core sitting on the boundary does not flip
between N and N - 1 every frame. Samples above 250 ms (a JIT warm-up, a
page-in) are dropped rather than dragging the average. Both instance
paths cost N + 1 steps, so one bound covers both.

A clamp is logged and, unless run_ahead_hide_warnings is set, shown once
on the OSD when it changes; recovery is logged only. The measurement and
the clamped count live in runloop_state_t next to the other runahead
state and reset with it in runahead_clear_variables().
2026-09-29 19:59:44 +02:00
LibretroAdmin bdf10bac35 audio: size the multichannel fold region at init instead of growing it on the batch callback
audio_driver_multi_fold_room() grew multi_fold with realloc() whenever a
batch slice was larger than any seen before. That is a heap operation
on the core's sample-batch callback - the one place in the audio path
that must not allocate.

Every multichannel entry (the discrete and folded paths of both the
int16 and float batch callbacks, and the rewind path) already slices its
batch to at most AUDIO_CHUNK_SIZE_NONBLOCKING >> 1 frames before
folding, so the buffer has a fixed upper bound of 1024 frames. Carve it
out of arena_float in audio_driver_init_internal() alongside the other
scratch regions, and turn the room check into a pure bound test. The
region is freed with the arena, so the separate free() goes too.
2026-09-29 19:59:40 +02:00
LibretroAdmin c45788af89 wdmks: run the refill thread in the Pro Audio class where asked
Time-critical is 15 in the normal class, and a display driver's worker
threads sit above it, which on a machine driving a 4K HDR swapchain is
where a 2.5 ms hole in a thread following a 2 ms loop comes from.  The
refill thread now takes the Pro Audio scheduling class under the same
setting the WASAPI pump takes it, falling back to time-critical where
the class is off, absent or refused; the teardown line names which.
2026-09-29 19:46:19 +02:00
LibretroAdmin 05be6626d1 audio: keep a frame of margin in the pipe where nothing regulates it
With Audio Sync and rate control off the pipe's fill is regulated by
nothing: it is whatever priming put there, and priming filled it to one
publish - which is a frame just after the core hands one over and
nothing just before the next, so a frame late by any amount reached the
device as silence.  The target on that path is two publishes now, a
frame's worth held at the worst phase, which is what a late frame costs.

Two things then kept the margin from lasting.  The trim after an
underrun cut the pipe back to the target without the device's share
priming had added on top, so the first pass after every prime threw
that share away as late audio and the fill settled a device buffer
short; the same figure now serves both.  And a stall that emptied the
pipe left it empty - a core paced to the display never runs ahead to
refill it - so silence found with the pipe short primes it again, the
wait the first pass made, while the device is silent anyway.

samples/audio/pipeline_clocked gains the lane: a 60 ms stall the core
does not catch up from, then three seconds of frames up to 12 ms late,
which must not reach the device.  It retries a run the host held the
process through, which its own wake lateness shows.
2026-09-29 19:46:15 +02:00
LibretroAdmin bb47929c1b audio: say at teardown whether the pipe ever ran short of the core
The driver's count of silence says the device went unfed; it does not
say by whom.  The threaded pipeline's consumer now keeps the other half
of the account - the passes after priming that found the pipe short and
had to wait for the core, the longest such wait, and the least the pipe
held at the start of a pass - and says so once beside the driver's line,
so a session with silence in it names the side that was late.
2026-09-29 19:23:24 +02:00
LibretroAdmin e9fd6fdb9e wdmks: say what the sampling floor means for a loop under 2 ms 2026-09-29 19:23:20 +02:00
LibretroAdmin b018d696a8 playlist: make the pushed entry's strings before moving the list
playlist_push_runtime() shifted every entry up and then filled the new
top one with strdup() results it never checked, so a copy that failed
partway left the list one entry longer with no path or no core on top,
and the next write put that on disk.  The strings are made first now,
and a push that cannot make them leaves the list as it was.

samples/playlist gains a lane that fails the copies at every point.
2026-09-29 19:23:17 +02:00
LibretroAdmin df1a0e3e9f wdmks: run the refill thread time-critical, sample at a quarter loop
The refill thread follows the hardware's position round the mapped
loop, and a pass that lands a loop late is a lap it cannot follow, which
the driver counts as a period of silence and resyncs across.  At a loop
of a few milliseconds that is one ordinary scheduling delay next to a
busy core, and the thread ran at normal priority.  It now raises itself
to time-critical where audio_thread_priority asks, as the WASAPI pump
does under the same setting, and a pin with no notification event is
sampled every quarter of the loop rather than every half, so a sample
late by up to three quarters is still inside the lap.

The thread keeps its own account of how it woke - the worst gap between
two passes and how many were a loop or more - and says so once at
teardown, beside which scheduling it ran under.

samples/audio/wdmks_loop gains a lane that runs the thread on the fake
register under both settings.
2026-09-29 19:01:21 +02:00
LibretroAdmin ec369ffc71 retro_eventcount test: take a last look at the count once the producer is done
The consumer could read the count, find it drained, then see the done
flag a producer set after adding one more item, and leave that item
behind: a fail one run in thousands, on any backend.
2026-09-29 19:01:17 +02:00
LibretroAdmin 7f783ebd0f retro_eventcount test: judge the bound by the held probes, not by burst expiry
Whether a bounded wait ever expires inside a burst depends on the
machine: a loaded runner under a sanitizer wakes every wait before its
bound, and the win32 event tier rounds the bound past the notifier's
period.  The held-notifier probes show the bound is honoured; the lane
now fails only when those all end early and no burst expired either.
2026-09-29 18:34:11 +02:00
LibretroAdmin f8346bed38 retro_eventcount: park on the Horizon address arbiter on Switch
svcWaitForAddress/svcSignalToAddress are the futex shape, so the Switch
build takes the lockless path on firmware 4.0.0 and newer and keeps the
condition variable only on older firmware, decided once per object at
init.

The test harness gains a SWITCH=1 build of the arbiter protocol on the
host, with the two system calls and the firmware query stood in over
futex; check runs it as current firmware and as pre-4.0.0.
2026-09-29 18:01:58 +02:00
LibretroAdmin d21573e7f9 retro_eventcount: keep the PS4 on the condition variable
The PS4 toolchain defines __FreeBSD__, but its libc carries neither the
POSIX clock the semaphore backend's bounded wait needs nor a semaphore
that backend can rely on, so it is kept off that backend the way
rthreads.c and retro_procbarrier.c keep it off the FreeBSD paths.
2026-09-29 17:36:57 +02:00
LibretroAdmin c6ed63f67c retro_eventcount: park on a per-thread semaphore on Darwin and the BSDs
The Windows backend's waiter list - stack blocks on the object, a spin
bit in the head pointer - gains a second sleep: a counting semaphore
of the waiting thread's own, made on its first park and kept in a
thread key for its life. A signal that lands before the wait is
counted rather than lost, the property the Windows ALERT tier has, so
no rendezvous is needed; a park is one wait and a wake one signal per
parked waiter, and nothing on either side takes a mutex. A bounded
wait that expires while a waker has taken its block consumes the
signal in flight, so the count never carries into the thread's next
park. The list is shared with Windows; only the sleep differs.

Darwin spells the semaphore in Mach - semaphore_create, semaphore_wait,
semaphore_timedwait, semaphore_signal - every call of which is in Mac
OS X 10.4's headers, so one binary runs from there up with no runtime
resolution; the BSDs spell it in POSIX. RETRO_EVENTCOUNT_FORCE_SEM
builds the POSIX spelling on any host, and the eventcount test's check
runs it beside the futex and scond backends. A new lane paces a
notifier to the consumer's bound so expiry and wake coincide, and
probes for a carried-over count after each burst with the notifier
held; on the semaphore backend the probe must expire.

The bench and test binaries that had been committed are untracked and
ignored.
2026-09-29 17:32:02 +02:00
LibretroAdmin 622c8649a6 audio_thread_wrapper: park on an eventcount, no lock
The wrapper's lock existed only to give its condition variable
something to sleep under. Both threads park on one retro_eventcount:
the audio thread in its stopped loop and its idle wait, the main thread
for the stop acknowledgement and for init() to return. Every change to
the words they test - alive, stopped, stopped_ack - is a release store
followed by a notify, and the re-check between prepare and commit is
what makes a request that lands in the window visible.

The stop acknowledgement answers the flag it read: block() clears the
ack before it raises the flag, so a thread that finds the ack clear
inside its wait window knows a start and a second stop landed since it
last answered, and answers again before parking. How init() ended, and
who owns the state when the frontend gives up waiting, is one word
moved by compare-and-swap from either side, so exactly one of them
frees it.

audio_thread_loop's ceiling in tools/lock_acquire_budget.ref is 0.
2026-09-29 16:32:28 +02:00
LibretroAdmin 990b294740 video_thread_wrapper: lock-free frame ring and pacing handoff
The frame ring's bookkeeping - the slot the video thread claims next,
how many filled slots wait, whether one is being rendered - is one
atomic word, frame.state, moved by compare-and-swap on both sides: the
push takes back the newest unclaimed frame with a CAS, the video thread
claims and completes with a CAS, so neither can win the same slot. The
ring's waiters park on a per-ring eventcount and the video thread on a
work eventcount, notified by whoever gives it something to do; the
menu-texture drain and idle wait slice their parks on the Apple main
thread so marshalled jobs still run. Slot contents stay ordered by the
publish and the claim.

The presenter's inputs to display pacing - period, next vblank, render
time - travel in the stats seqlock, so the hold reads them as one
snapshot; drain requests, repeat requests, the content period and the
main thread's core time and pacing flag are atomic words; the wrapped
driver's poke table is installed on the video thread through a
blocking command, the thread that reads it every pass. Released
textures ride an mpsc_stack to the next push. thr->lock covers the
command channel and the upload lists only, and video_thread_frame
takes no lock at all: its ceiling in tools/lock_acquire_budget.ref is
0, and video_thread_loop is measured there too.

The threaded_video harness reads the video thread's own fields and
swaps its drivers in through blocking commands on that thread, and
takes the repeat and swap counts in one read.
2026-09-29 09:10:17 +02:00
libretroadmin 98b2dcc478 net_krb5: prefix the DER helpers
The griffin unit includes net_krb5.c next to x509.c and net_smb2.c,
which have static der_peek and der_len of their own with other
signatures, so the build failed on the conflicting types. Every DER
helper, type and tag macro in net_krb5.c now carries a krb5_ / KRB5_
prefix; nothing else changes.
2026-09-29 09:09:23 +02:00
libretroadmin 62efd20b75 test_keychain: tamper with a character that always changes the octets
The tamper case flipped the low bit of the third-last base64
character, and one time in 64 that character was 'A', whose flip
'@' is outside the alphabet; unbase64 does not reject it and reads
it as 'A' again, so the sealed value decoded unchanged, opened, and
the assertion failed at random. Swap the character for a different
alphabet character instead, so the decoded octets always differ and
the AEAD tag always refuses them.
2026-09-29 09:02:45 +02:00
LibretroAdmin f2c0af37d6 libretro-common: add convert_float_to_s32, used by wdmks
Full-scale float to 32-bit integer with a valid-bits mask for
24-in-32, saturating with NaN to 0, in the shape of
convert_float_to_s16: an SSE2 arm, a NEON arm enabled from
cpu_features at init, and a scalar tail. The wdmks driver's write path
converts through it for the wide integer pins.

libretro-common/samples/audio/float_to_s32 checks every arm against a
scalar reference on the edges and at every tail length; it runs
natively for SSE2 and cross-built under qemu-aarch64 for NEON, and is
registered in the samples workflow.
2026-09-29 09:02:33 +02:00
LibretroAdmin 6e8ab65c17 wdmks: take 32- and 24-in-32-bit integer pins, propose float last
A pin whose ranges name only 32-bit PCM - a USB Audio Class 2 DAC as
usbaudio2.sys reports it - had nothing proposed to it and the driver
gave up with no format tried. Both wide integer shapes are proposed
now: the render side converts the frontend's float frames into them
on the write path, the capture side converts them back into float, and
either is the same size as float so nothing about a write or a read
changes. The order is float, 32, 24 in 32, 16. Float is also offered
once more as a last resort to a pin with a 32-bit-wide range and no
float subtype, since such pins have taken it when asked.

samples/audio/wdmks_loop gains a lane for the pin shape and both
conversions.
2026-09-29 08:47:06 +02:00
libretroadmin 91caaca299 net_krb5: the Kerberos client - AS, TGS and the GSS initial token
On the enctype layer, the protocol: a DER writer that closes
constructed values by moving their content up under the header (the
lengths stay minimal, as the KDCs require), a matching reader, and
the three exchanges. krb5_get_tgt sends an AS-REQ without
preauthentication and, when the KDC answers KDC_ERR_PREAUTH_REQUIRED
with ETYPE-INFO2, retries with PA-ENC-TIMESTAMP under the key from
the salt and enctype it named; the KDC's own time in the KRB-ERROR
corrects our clock for everything after. krb5_get_service_ticket
runs the TGS exchange with an AP-REQ of the TGT in PA-TGS-REQ (the
req-body checksum with usage 6, the authenticator with 7, the reply
under the TGT session key with 8). krb5_gss_init_token wraps an
AP-REQ for the service in the RFC 4121 initial context token with the
0x8003 checksum, mutual authentication and a fresh subkey, which is
the mechToken a SPNEGO NegTokenInit carries; krb5_gss_accept_token
takes the AP-REP and settles the context key. All over TCP with the
four-octet length, one connection per exchange, timeouts throughout;
no snprintf, frames under 1.4 KiB.

samples/network/krb5_retro/krb5_test drives the client against a KDC
and, given the service key, proves the ticket in the token is real by
decrypting it with that key (usage 2) and finding the client name
inside. local_kdc_test.sh stands up a MIT krb5kdc on a fixed port with
a realm, a user that requires preauthentication, one that does not,
and a cifs/ service whose key it reads back from its keytab, then
runs both paths, a wrong password against each, and an unknown
service. The network-clients workflow installs krb5-kdc and runs the
matrix natively, under ASan/UBSan/LSan and with the Windows build
under Wine. The crypto and keychain suites now link the socket layer
alongside net_krb5.c.
2026-09-29 08:44:32 +02:00
LibretroAdmin 0298cd093c wdmks: add the microphone driver
The capture side of kernel streaming, as microphone_wdmks: the same
filters enumerated under the capture alias, a pin audio flows out of
opened for reading, mono first and stereo folded down where the pin
captures nothing narrower. WaveCyclic pins are read with
IOCTL_KS_READ_STREAM packets; WaveRT pins through the mapped loop, read
from behind the hardware's position, with lost laps and overruns moving
the read cursor up to the hardware. Every wait is bounded, and the
packet reclaim on close is the render side's.

The WaveRT buffer, register and notification-event requests and the
device scan and pin open take the pin handle and direction, so both
sides share them. samples/audio/wdmks_loop gains lanes for the fold and
the capture loop model.
2026-09-29 08:36:53 +02:00
LibretroAdmin 73d582ca40 wdmks: match formats against each pin range, read the register at its width
A pin's data ranges are kept apart rather than folded into one box, so a
candidate format is proposed only when a single range holds its rate,
width, channel count and sample type together: a pin offering 48 kHz
stereo and 192 kHz 8-channel no longer has 192 kHz stereo asked of it.
Past twelve ranges the rest widen the last kept range of their type.
Pin records move to the heap with the device that owns them.

The WaveRT position register is read at the width the pin reports. A
64-bit register is a byte count, and its low word alone cannot place
the hardware once the count passes 2^32 on a loop that is not a power
of two; a 32-bit build reads the high word twice so it is never torn.

samples/audio/wdmks_loop gains lanes for both.
2026-09-29 08:24:25 +02:00
libretroadmin f61d348b25 net_krb5: swap the CTS blocks through temporaries
The whole-block ciphertext-stealing swap copied one block of the
output buffer over another. The two blocks are adjacent and do not
overlap, but the overlap-copy check keys on any copy within the same
buffer, so take both blocks into temporaries first. The RFC 3962 CTS
vectors pass as before.
2026-09-29 07:56:01 +02:00
LibretroAdmin 2279e1bbaa gfx_widgets: lock-free SPSC ring for pending messages
Every gfx_widgets_msg_queue_push() caller is main-thread code -
runloop_msg_queue_push() defers off-main callers before it gets there
and the task queue's progress push runs from its main-thread gather -
and the consumer is the thread that owns the widgets, so the pending
ring has one producer and one consumer. It is now the retro_spsc
shape: the producer owns msg_queue_tail and the consumer
msg_queue_head, free-running counts indexed by mask, each published
with a release store and read across with an acquire load.
msg_queue_lock is gone.

gfx_widgets_msg_queue_race_test builds the production push and pop
out of gfx_widgets.c and drives them from one producer thread and one
consumer thread, checking order, loss, full-ring refusal and the
consumer handover at deinit, under ThreadSanitizer in CI. The
snapshot-ownership test models a full ring through the cursors.
tools/libretro-common-link-check.sh also scans tests-other/.
2026-09-29 07:53:09 +02:00
libretroadmin 05e14927db crypto: AES inverse cipher, HMAC/PBKDF2-SHA1, Kerberos AES enctypes
The first layer of Kerberos for the built-in SMB client: the AES
enctypes of RFC 3962 (aes128/aes256-cts-hmac-sha1-96), which every
Active Directory domain and MIT/Heimdal realm offers. net_krb5.c
carries n-fold, DK, CBC with ciphertext stealing, string-to-key,
encryption with the confounder and truncated HMAC, checksums and the
PRF, on the cleanroom AES and SHA-1 alone; it is built with
HAVE_RETROSMB and the protocol (AS/TGS/AP exchanges, GSS tokens,
SPNEGO in SESSION_SETUP) goes on top of it next.

CTS needs the inverse cipher, which no mode so far did, so crypto.c
gains aes_decrypt_block on all three paths: the portable one runs the
standard inverse order on the encryption schedule with InvMixColumns
on the state, AES-NI applies aesimc to the round keys per block and
ARMv8 vaesimc likewise, so struct aes_ctx and every existing caller
stay as they are and no decryption schedule is cached. kdf.c gains
HMAC-SHA1 and PBKDF2-HMAC-SHA1 for the enctypes' checksum and
string-to-key, in the shape of the SHA-256 ones.

The crypto suite pins all of it: FIPS 197 C.1-C.3 for the inverse
cipher plus round trips through this build's own path, RFC 2202 and
RFC 6070 for the SHA-1 pair, all seven RFC 3961 A.1 n-fold vectors,
all six RFC 3962 B CTS vectors (encrypt, and decrypt in place), all
RFC 3962 B string-to-key vectors, and encrypt/decrypt round trips
with tampering, wrong usage and short input refused. The suite runs
natively, under qemu-aarch64 on both -march variants and as the
Windows binary under Wine.
2026-09-29 07:38:26 +02:00
LibretroAdmin 52634c32db Link retro_eventcount in every harness that builds rzip_stream.c
rzip_stream.c parks its chunk-writer pool on retro_eventcount, so the
content_prefetch sample and the companion test scripts (core, cocoa,
cocoa_macos, win32), which compile it with HAVE_THREADS, link
rthreads/retro_eventcount.c alongside rthreads.c.

tools/libretro-common-link-check.sh builds every sample Makefile in
the tree that names a given libretro-common unit and lists the
tools/*.sh harnesses that do, so a unit gaining a dependency on
another libretro-common TU is caught at link time across all of
them before delivery.
2026-09-29 07:38:09 +02:00
LibretroAdmin 0ca0d328ad rzip_stream: lockless handshake for the parallel chunk writer
Each pool slot is owned by one worker and touched by exactly two
threads, so the slot status is an acquire/release atomic word that
orders the slot's inputs and outputs, and parking goes through
retro_eventcount: one per slot for its worker, notified only by the
writer, and one for the writer, notified by whichever worker finishes.
A dispatch or a completion wakes exactly the thread waiting for it.
Output stays byte-identical to the serial writer.

rzip_parallel_test builds rzip_stream.c with HAVE_THREADS, drives the
pool at every width (the serial fallback, two, eight, and a count past
RZIP_MAX_THREADS) with the core count wrapped at link time, and
compares each result to the serial file byte for byte, both codecs,
including a stream written in pieces across pool wake-ups and two
hundred short pool lifetimes. It runs in the samples workflow under
ASan/UBSan and under ThreadSanitizer. The other sample Makefiles that
build rzip_stream.c with HAVE_THREADS link retro_eventcount.c.
2026-09-29 07:07:59 +02:00
LibretroAdmin fe9feb9ee8 rzstd: make the predefined-table pointer array a constant
rzstd_encode()'s predefined-table pointer array is a table of address
constants, so it is initialised once as const and concurrent encoders
only ever read it. Clears the ThreadSanitizer report on parallel
Zstandard writes.
2026-09-29 07:07:59 +02:00
libretroadmin 90092e4425 nfs_retro: local_ganesha_test.sh runs from its own directory
The other local_*_test.sh scripts cd to their own directory before
building; this one did not, so invoked from the repository root (as
the workflow does) its bare make hit RetroArch's top-level Makefile
and stopped on the missing config.mk.
2026-09-29 06:27:37 +02:00
libretroadmin 2b9fb492e7 menu: NFS Version entry in the NFS Client settings
nfs_version had a config key but no menu entry, so choosing NFSv4
meant editing retroarch.cfg. Add it to the NFS Client group next to
the ports as a 3..4 range with a sublabel explaining what each
version needs from the server.
2026-09-29 06:09:17 +02:00
LibretroAdmin c28019cc73 task_content: build the .part staging name with strlcpy/strlcat
Avoids the -Wformat-truncation warning from formatting a PATH_MAX_LENGTH
path plus a suffix into a buffer of the same size.
2026-09-29 05:55:01 +02:00
libretroadmin 859132bd9d net_nfs3: NFSv4.0 client behind the same rnfs API, nfs:// version setting
Add NFSv4.0 (RFC 7530) to the built-in NFS client, selected per
connection with rnfs_set_version(c, 4): one COMPOUND per operation
over a single TCP connection to port 2049, no portmapper or MOUNT
service, the export given as the server's pseudo-filesystem path.
SETCLIENTID/CONFIRM establishes the client, OPEN/OPEN_CONFIRM with a
per-connection open-owner and advancing seqid creates files, READ and
WRITE use the anonymous stateid, SETATTR truncates, CREATE/REMOVE/
RENAME (SAVEFH: source dir saved, target dir current) and READDIR
with fattr4 cover the rest. NFS4ERR_GRACE and DELAY are retried on a
fixed bound so a restarting server never blocks a caller forever;
the wait is a bare select so no timer header is pulled in. Large
COMPOUND buffers are heap-allocated so every frame stays under 2 KiB
including where the helpers inline into rnfs_connect. The client
verifier and open-owner are derived without shared state, and the
VFS backend's last-error string now goes through a ring of slots
claimed by an atomic counter, so the pooled threads test is clean
under TSan for both versions. Nothing here depends on the crypto
code, so the file still links without HAVE_CRYPTO.

The nfs:// backend gets nfs_settings.version and RetroArch a
nfs_version config key (default 3, unchanged behaviour). The NFS
samples take the version as a fifth argument, local_ganesha_test.sh
runs the same four lanes as the unfs3 matrix against nfs-ganesha,
and the network-clients workflow runs it natively, under ASan/UBSan/
LSan and as the Windows tools under Wine.
2026-09-29 05:42:13 +02:00
libretroadmin f632672461 x509: decode certificate validity without 32-bit long overflow
der_read_time multiplied the day count by 86400 in long, which is 32
bits on Windows on both architectures, so any notBefore/notAfter past
2038-01-19 wrapped negative and the certificate read as expired. The
leaf-policy test vectors run to 2046 and tripped this in the Wine lane
while the Linux run, with a 64-bit long, passed. Do the arithmetic in
int64_t, and where time_t itself is 32 bits saturate rather than wrap
so a far-future notAfter stays in the future.
2026-09-29 05:42:10 +02:00
libretroadmin 1b32c75460 CI: the network client job installs the wine launcher
The job installed wine64 alone, which on the runner brings the 64-bit
loader but not the wine command the Windows lanes run the tools
through; the unit-test job had always installed wine as well. With
the launcher missing the Wine step's own check skipped cleanly, but
the matrices then ran their MinGW builds through nothing and failed
with command not found. The job installs wine with wine64 and
asserts the launcher is present before the lanes run.
2026-09-29 04:28:45 +02:00
libretroadmin b9e70fca38 crypto suite: leaf policy vectors
The certificate policy the verifier gained from the audit - unknown
critical extensions refused, an extendedKeyUsage without serverAuth
refused, a keyUsage that cannot sign refused - was covered by the
server matrix only, which needs openssl at hand. The crypto suite now
carries an independent P-256 CA and six leaves under it, valid for
twenty years: serverAuth with digitalSignature and an unknown
non-critical extension pass; an unknown critical extension, a
clientAuth-only EKU, a codeSigning-only EKU and a keyUsage without
digitalSignature are refused. Runs natively, on both limb widths,
under qemu-aarch64 and as the Windows build under Wine.
2026-09-29 04:26:48 +02:00
libretroadmin ae200e0ed1 smb:// and nfs:// backends: connection pools without locks
Both backends had one lock around every operation, held across the
network I/O, so a read on one connection blocked every other thread's
access to that backend and a stalled server held up all of them. The
pools are now lock-free. Each slot is a connection and a busy flag; a
caller takes a slot by swapping the flag from 0 to 1, uses the
connection alone, and swaps it back - no waiting anywhere. When every
slot is busy the caller connects privately for its own use and drops
that connection afterwards: slower, but never blocked behind another
thread. The pool itself is published with a CAS by whichever caller
builds it first, and a pool whose slots are still held is left alone
by a settings change (callers connect privately until it is idle).

The two protocols place their handles differently: an SMB file or
directory handle belongs to its session, so an SMB stream holds its
slot from open to close and a listing from opendir to closedir, while
stat and share enumeration borrow one per call; an NFS file handle is
valid on any connection to the export, so an NFS stream holds nothing
and every read borrows a slot. Slots connect on first use, and the
SMB auth-mode probe settles per pool from the first connection.

Both backends are clean under ThreadSanitizer with four threads on a
two-slot pool, which exercises the private-connection path; the SMB
matrix gains that test beside the NFS one. Both pass native, under
ASan and as the Windows builds; without threads the atomics compile
to plain operations and the objects carry no thread references.
2026-09-29 04:18:21 +02:00
libretroadmin d8f72f74d2 CI: the network client lanes get a workflow of their own
The libretro-common test job has a fifteen-minute budget, and the
network lanes that accumulated in it - three server matrices, the
Windows builds under Wine, the sanitizer rerun, the fuzz targets and
building unfs3 from source - now take it past that, so the runner
cancels whichever step is running when the clock runs out; the last
run died in the Cocoa companion harness, which has nothing wrong with
it. The six network steps move to Linux-network-clients.yml with a
budget of their own and only the packages they need; the unit-test
job is back to what it was before the network stack arrived.
2026-09-29 04:18:17 +02:00
libretroadmin 4d4d85b4f2 Network stack: the audit's three P1 findings and three P2s
Trust store and curves: the X.509 trust store is process-wide and
the first handshakes on the updater, achievements and cloud-sync
threads raced to build it - free, reallocate and publish it
field by field. One lock now covers building, freeing and every
chain walk over it, and a load of the source already loaded is the
no-op every later handshake takes. The same test found a second
first-use race in pk.c: the curve constants were computed on first
use under a "every thread writes the same values" argument, which
lets a thread publish the ready flag while another is still writing
a constant a third one reads. The computation runs under a lock,
with an acquire load of the flag on the fast path. Six threads making
their first connections at once are clean under ThreadSanitizer
(seven reports before), and the case is in the matrix.

Certificate policy: an extension marked critical that the verifier
does not understand now makes the certificate unusable, as RFC 5280
requires; extendedKeyUsage is parsed and a leaf whose EKU lacks
serverAuth (client-auth or code-signing only) is refused, as is a
leaf whose keyUsage cannot sign the handshake or that is itself a CA
in a chain. Six matrix cases cover the refusals and the two shapes
that must still pass. The 155-certificate bundle still loads.

Staging: a core without VFS support had its remote content copied to
<cache>/<basename>, which overwrote any file of that name already in
the cache and deleted it on unload. The copy now goes into a private
directory made for the connection, under a part name until complete,
then renamed; file and directory are registered for removal only
once they exist, the directory after its file. Verified with the
real binary: an unrelated <cache>/vfs.bin survives the run.

Also from the audit: the smb:// / nfs:// scheme check read six
octets of any path (string_starts_with now); the built-in TLS
backend's verification mode is an atomic that each connection copies
at creation, like the mbedtls backend already did; and net_http
refuses a source callback claiming more bytes than the buffer it was
handed.
2026-09-29 03:53:25 +02:00
libretroadmin 609f485534 libretro-common: fuzz the network parsers
libFuzzer harnesses under test/fuzz for the four parsers that read
bytes off the network: the X.509 parser (the input as a DER
certificate and as a PEM bundle), and the TLS, SMB and NFS clients'
receive paths - a listener in the harness accepts every connection
the client makes and answers with the fuzzer's input, so the real
socket and parsing code runs against arbitrary bytes. Seed corpora
were recorded off openssl s_server (1.2 and 1.3, RSA and ECDSA), Samba
and unfs3 through a small relay, which is what gets the fuzzer past
the first framing checks. Two minutes per target under ASan and UBSan
found nothing; the libretro-common CI job runs thirty seconds per
target and fails on any artifact. The SMB client gains rsmb_set_port()
for the harness, which is also what a server on a non-standard port
needs.
2026-09-29 03:25:30 +02:00
libretroadmin a8211af1be TLS client: the AES-256-GCM-SHA384 suite for 1.3
The third TLS 1.3 suite runs its whole key schedule over SHA-384:
48-octet secrets, HKDF and HMAC over that hash, the SHA-384 transcript
the client already keeps for 1.2, a 32-octet AES key. The 1.3 code
now takes the hash length from the suite everywhere it had assumed
SHA-256 - the secrets, Derive-Secret, the Finished MACs, the
CertificateVerify content, the ticket PSK, the binder (over the hash
of the suite the ticket was issued under) and KeyUpdate - and kdf.c
gains HKDF over SHA-384, checked against an independent computation
in the crypto suite. Servers restricted to this suite, which
compliance profiles such as CNSA call for, now connect and resume.
Two matrix cases: the suite on an RSA certificate, and on ECDSA with
three rounds resuming.
2026-09-29 03:01:39 +02:00
libretroadmin 76dd734ccb tls_retro matrix: KeyUpdate in both directions
The 1.3 KeyUpdate handling had no test: nothing in the matrix ever
rotated keys mid-connection. tls_keyupdate drives openssl s_server's
console - lines to the client, 'K' for a KeyUpdate with update
requested, 'k' for one without - and exchanges a line with the server
across each: the client must keep reading under the server's new keys
and, on the requested one, rotate its own so the server can read the
reply, which the server's output shows. Both directions pass, native,
under the sanitizers and as the Windows build under Wine.

The bench and this tool no longer connect the socket before handing it
to the client, which connects it itself: a second connect passes on
Linux but Winsock refuses it, which the Wine run caught.
2026-09-29 02:52:42 +02:00
libretroadmin bd9e70eceb TLS client: X25519 key exchange
X25519 is the group most servers prefer and some allow alone; the
client offered P-256 and P-384 only, so a 1.3 server wanting X25519
had to retry, which the client refuses, and a 1.2 server restricted to
it could not connect at all. pk.c gains RFC 7748 X25519 - the
Montgomery ladder over 2^255 - 19 on the field arithmetic the curves
already use, scalar clamping, the all-zero result refused - checked
against the RFC's vectors in the crypto suite on both limb widths.
The ClientHello offers x25519 ahead of the NIST groups and carries an
X25519 share beside the P-256 one, so no 1.3 server needs a retry;
the 1.2 ServerKeyExchange accepts the group too. Four matrix cases
pin each group alone on each version.
2026-09-29 02:41:44 +02:00
libretroadmin a0a3840dc0 Cleanup 2026-09-29 02:33:09 +02:00
libretroadmin 50922acca4 TLS client: 1.3 PSK resumption, and no Nagle on TLS sockets
With 1.3 negotiated everywhere, every connection had gone back to a
full handshake. The client now keeps the resumption secret from each
1.3 handshake, turns the server's NewSessionTicket into a PSK
(Expand-Label over the ticket nonce, the ticket as identity, its
age obfuscated as the server asked) and offers it in the next
ClientHello with psk_dhe_ke and a binder computed over the truncated
hello. When the server selects it the early secret comes from the
PSK and the server's flight is EncryptedExtensions and Finished
alone; when it does not, the handshake runs in full as before. The
cache entry for a host is either a 1.2 session or a 1.3 ticket. Real
hosts resume on the second connection; the matrix gains three cases:
three rounds on AES-GCM, two on ChaCha20, and a server issuing no
tickets, where no round resumes.

Measuring the resumed handshakes with a request and reply attached
showed the 1.2 abbreviated case at 44 ms: the first request went out
while the client's Finished was still unacknowledged and Nagle held
it for the server's delayed ACK. TLS sockets now set TCP_NODELAY
where the platform has it - everything RetroArch does over TLS is
request-then-reply, so there is nothing for Nagle to coalesce -
which puts a resumed 1.2 connection with its first exchange at
0.7 ms and a resumed 1.3 one at 1.6 ms on loopback.
2026-09-29 02:29:33 +02:00
github-actions b429d60a24 Fetch translations from Crowdin 2026-09-29 00:31:04 +00:00
libretroadmin 2361aa9796 CI: the sudo Wine run keeps its prefix where root may create one
Wine refuses to create a prefix inside a directory it does not own,
and $RUNNER_TEMP belongs to the runner user, so the SMB matrix under
sudo could not start its Windows tools. The root run now uses a
prefix under /tmp with root's own HOME, initialised with wineboot
before the first tool runs.
2026-09-29 02:14:28 +02:00
libretroadmin fe4c9f894b TLS client: TLS 1.3
The ClientHello now offers TLS 1.3 ahead of 1.2 - supported_versions,
a P-256 key_share, the two SHA-256 suites (AES-128-GCM and
ChaCha20-Poly1305) and the rsa_pss_rsae signature schemes 1.3
requires - and the ServerHello decides which handshake runs. For 1.3
that is the RFC 8446 1-RTT flow: the HKDF key schedule from the
shared secret and the running transcript, handshake traffic keys for
EncryptedExtensions, the 1.3-shape Certificate (verified through the
same chain code), CertificateVerify over the transcript hash (ECDSA
P-256 / P-384, or RSA-PSS with MGF1 and a hash-length salt, which
pk.c gains and the crypto suite checks against OpenSSL signatures in
both hash sizes plus four refusals), the Finished MACs both ways, then
the application keys. Records take the 1.3 shape: outer type
application_data, the real type inside, iv XOR sequence as the
nonce, the header as AAD; the compatibility ChangeCipherSpec is sent
and tolerated. After the handshake, tickets on the application stream
are dropped (no PSK resumption yet) and a KeyUpdate rotates the
server keys and is answered when asked. A HelloRetryRequest is
refused since every 1.3 server takes P-256; a server offering only a
suite we do not carry gets no downgrade, just no connection.

Offering the PSS schemes lets a 1.2 server sign its
ServerKeyExchange with them too, so the 1.2 verifier takes them as
well. The 1.2 matrix is unchanged and green; nine 1.3 cases join it
(RSA and ECDSA certificates, both suites, a P-384 signature, an
intermediate chain, untrusted CA and wrong host refused, the
unsupported-suite case), all passing native, under the sanitizers and
as the Windows build. Real hosts negotiate 1.3 with it; tls_fetch
now prints the version.
2026-09-29 02:10:48 +02:00
libretroadmin 3b64466dcb smb_retro matrix: take port 445 from a distribution smbd first
The runner's samba package comes up as a service on 445, so the
matrix's own smbd could not bind and every case reached the
distribution server instead: it maps an unknown user to guest (the
unsigned-session failure seen earlier) and has no [share], hence
tree connect refused with STATUS_BAD_NETWORK_NAME. The script now
checks the port before the first case, stops the service and any
other smbd holding it, and fails plainly if it cannot be freed.
Reproduced with a second smbd on 445 and confirmed the guard takes
it over.
2026-09-29 01:55:04 +02:00
libretroadmin 250bedcb87 tls_bench: bulk receive throughput mode
tls_bench host port ca get path fetches a file from an openssl
s_server -WWW and reports the receive rate through the client's
record path. Against a local server: AES-128-GCM 560 MB/s and
AES-256-GCM 552 MB/s, the same server limit openssl s_client reaches
(565 MB/s); ChaCha20-Poly1305 238 MB/s, the software cipher, still
well above any home link. No client change came out of the
measurement.
2026-09-29 01:43:32 +02:00
libretroadmin 601c9de9aa SMB and NFS clients: transfers up to 1 MiB per request
Both clients moved data 64 KiB per request, so a large read was a
round trip per 64 KiB: fine on loopback, the bottleneck on a real
network. The SMB client now does multi-credit I/O on 2.1 and later -
CreditCharge of one credit per 64 KiB, the message id advanced by the
charge, a running credit balance from each reply's grant, requests
clipped to what the server has granted - up to 1 MiB, the negotiated
MaxRead / MaxWrite, or 64 KiB on 2.0.2, which has no multi-credit;
the connection's buffers grow to the negotiated size once the
NEGOTIATE reply has been read. The NFS client asks FSINFO for the
server's rtmax / wtmax after the mount and sizes its transfers and
buffers to that, up to 1 MiB.

On loopback, where round trips are nearly free, SMB reads go from 256
to 343 MB/s and writes from 248 to 320 MB/s against Samba; NFS
against unfs3 from 1.5 to 2.1 GB/s reads and 181 to 414 MB/s writes.
Against a server across a LAN the gain is the sixteen-fold drop in
round trips per megabyte. Both matrices pass native, under ASan
(which caught the NEGOTIATE parser reading its reply after the
buffer had been grown, now done afterwards) and as Windows builds.
2026-09-29 01:28:58 +02:00
libretroadmin b3519a31ce SMB client: do not verify signing on a guest or anonymous session
A guest or anonymous/null session is returned unsigned even when the
server requires signing, so a server that maps failed or empty
credentials to guest answers the final SESSION_SETUP with SUCCESS and
no signature. The client read that as a bad signature and refused the
connection - intermittent in the field, since whether a login lands on
a real or a guest session depends on the server's mapping. The session
setup now reads SessionFlags before checking the response: on
SMB2_SESSION_FLAG_IS_GUEST or IS_NULL it turns signing off for the
session and skips the check, as MS-SMB2 3.2.5.3.1 requires. The samba
matrix gains a guest-mapping server (map to guest, mandatory signing)
that reproduced the failure exactly before the fix.
2026-09-29 01:19:59 +02:00
libretroadmin e28f750752 CI: a Wine prefix of its own for the SMB run under sudo
The SMB matrix runs under sudo for port 445, and Wine refuses to use a
prefix owned by another user, so the Windows build of the SMB tools
failed before connecting. That run now gets a root-owned prefix, and
its clean runs under sudo too so the root-built tools can be removed.
The shim's fixture registration is a macro, which stops the unused
function warning in suites that register none.
2026-09-29 01:06:13 +02:00
libretroadmin 96cffab5ed SMB client: send the NetBIOS prefix and the frame in one write
Every request cost 44 ms on loopback. The four-octet NetBIOS length
prefix went out as its own write and the frame as a second one, and
the frame sat in Nagle's algorithm behind the unacknowledged prefix
until the server's delayed ACK - the same stall the TLS handshake
had, here on every SMB request: each directory listing, stat and read
the menu browser makes. The transmit buffer now keeps four spare
octets ahead of the frame and prefix plus frame leave together;
0.02 ms per request on the same loopback, and the samba matrix that
used to run for minutes takes fifteen seconds.

The SMB and NFS test tools print their per-request latency so a stall
of this shape shows in the CI log. NFS was already a single write.
2026-09-29 00:51:37 +02:00
libretroadmin e28044d649 TLS client: send each handshake flight in one write
A full handshake took 47 ms against a local server and a resumed one
0.1 ms, which no amount of public-key work explains. The client wrote
ClientKeyExchange, ChangeCipherSpec and Finished as three small
records, and the second of them sat in Nagle's algorithm waiting for
the server's delayed ACK of the first: a 40 ms stall on every full
handshake, on every platform, to every server. The flight is now
corked into one buffer and written once, and the resumed flight's
CCS and Finished the same way; the socket keeps its normal options.
Full handshakes drop to 2.9 ms on the same loopback, RSA and ECDSA
suites alike.

samples/network/tls_retro/tls_bench measures full and resumed
handshake time against a server; it is what found this.
2026-09-29 00:40:58 +02:00
libretroadmin fa518115af Run the Windows builds of the network stack under Wine
The MinGW builds of the crypto and keychain suites and of the three
network test tools were only ever linked; now they run. Makefile.test
gains win32-wine-check, which builds the crypto and keychain suites
with the libcheck shim as Windows binaries and runs them under Wine -
RtlGenRandom, the MachineGuid machine identity, the Win32 cpu-feature
dispatch onto AES-NI - and the three server matrices take RUNNER=wine
EXE=.exe so the MinGW tools drive the same local openssl, Samba and
unfs3 over Winsock, the four-thread NFS test on the Win32 thread
backend included. All of it passes; the libretro-common CI job runs
it with wine64 installed.

Two things the run needed: the shim now runs checked fixtures around
each test, as libcheck does, since the keychain suite sets its key
file up in one (nothing ran it, so the suite saw an empty path); and
that suite takes its temp dir from TMP / TEMP before falling back to
/tmp, which a Windows build cannot write.
2026-09-29 00:34:12 +02:00
libretroadmin 6095cd97d9 Network backends: serialise the shared pools and the TLS session cache
The VFS is entered from several threads at once - content loading,
cloud sync, thumbnail and playlist scans - and the smb:// and nfs://
backends hand those threads contexts from a shared pool whose
connection is not safe to use from two of them, with the pool's own
bookkeeping racing on top. ThreadSanitizer reports 72 data races from
four threads on a two-slot pool, and the reads come back corrupted.
Each backend now takes one lock around every entry point (HAVE_THREADS
only; the lock is published once with a CAS so two first callers
cannot each install their own): calls to one server serialise, which
is no loss on a network-bound path. The TLS client's session cache,
the one thing its connections share, is guarded the same way, and the
ClientHello copies the cached id and ticket out under the lock rather
than reading the slot while it is written.

The unfs3 matrix gains a four-thread test through the nfs:// VFS
backend, clean under ThreadSanitizer with the locks and failing without
them. Without threads every lock compiles to nothing; the three units
build with the tree's --disable-threads defines and carry no rthreads
symbol.
2026-09-29 00:34:09 +02:00
libretroadmin 15ebb6c825 tls_retro matrix: certificate chains with an intermediate CA
Every case so far had the leaf signed by the trust root directly; real
servers send leaf plus one or two intermediates and the client trusts
only the root. Four cases cover that: an RSA intermediate, a P-384
intermediate under the RSA root, the intermediate itself as the trust
anchor, and a server that omits the intermediate, which must be
refused. The intermediate is handed to s_server with -cert_chain, as
its -cert takes only the first certificate of a file.
2026-09-29 00:16:53 +02:00
libretroadmin 8e781c0806 lrc_hash: hex output without snprintf in sha256_hash()
The hex loop used snprintf, which the strict C89 lane rejects since
-ansi does not declare it; a nibble table writes the same 64 lowercase
digits and the terminator. Verified against the FIPS 180-4 "abc"
vector; the one caller (cloud sync's S3 signing) already sizes for it.
2026-09-29 00:12:36 +02:00
libretroadmin 0ff5ce4383 Fix three CI failures from the NFS menu, sanitizer and legacy lanes
The NFS menu declarations - the MENU_LABEL rows, the deferred-list
enum, the display-list and action enums, the settings-list enum and
the deferred-push / ok-list cases - had been added inside the
neighbouring HAVE_SMBCLIENT blocks, and the netplay action def file
that carries the NFS settings entry was only consumed under
HAVE_SMBCLIENT. Every shape with NFS and without SMB failed to compile,
which is what the compile matrix's NFS-with-menu gate and the griffin
HAVE_RETRONFS shape build. Each declaration now sits in its own
HAVE_NFSCLIENT block and the def-file regions take either client.

The sanitizer lane builds unfs3 from source with the sanitizer CFLAGS
still exported, and a server built that way does not come up, so the
NFS run could not mount. The script now builds it with the flags
unset, and the test tool frees its context on a failed check so a
real failure is not buried under a leak report.

The legacy-backend job rejected x509_verify_chain in the binary; it is
part of HAVE_CRYPTO, which the keychain needs whichever TLS backend is
selected. The check now asserts on client symbols only.
2026-09-28 23:50:54 +02:00
libretroadmin e76051ba58 Network clients: sanitizer sweep, SHA-2 stream empty updates
The TLS, SMB and NFS server matrices were run with the clients built
under ASan, UBSan and LeakSanitizer. UBSan caught one thing: the
SHA-384 PRF path hands the HMAC an empty second seed as a NULL pointer
with length zero, and the SHA-256 and SHA-512 stream updates passed
that straight to memcpy, which is undefined even for zero bytes. Both
updates now return at once on an empty length. Everything else -
14 TLS cases, 8 SMB dialect and sealing cases with the VFS on top,
the NFS run with the VFS on top - came through clean, no leaks.

The libretro-common CI job now runs the three matrices a second time
under the sanitizers, so any memory error a real server can drive is
caught there rather than on a user's machine.
2026-09-28 23:18:29 +02:00
libretroadmin ef4c9714eb CI: build with the legacy TLS and SMB backends selected
The built-in clients are now the defaults, so nothing in CI builds the
mbedtls backend or the bundled libsmb2 any more - the configuration a
packager pinning a system library, or a platform that keeps one of
them, actually uses. This job configures with every built-in client
off (system mbedtls, bundled libsmb2, no NFS), builds to the link, and
checks the binary carries the legacy backends and none of the built-in
client symbols. Locally, full builds with --disable-networking and
with --disable-retrossl both link, and the flag-sensitive units
(configuration, retroarch, task_content, the VFS, the menu units,
cloud sync) compile under --disable-crypto, --disable-networking and
the clients disabled one by one.
2026-09-28 22:39:16 +02:00
libretroadmin 407a9d6ac1 TLS client: session resumption by id and by ticket
Every reconnect to the buildbot or the achievements server was a full
handshake: certificate chain, two signature verifications and an ECDHE
exchange. The client now keeps a small per-process cache (eight
hosts, six hours, never on disk) of the master secret, suite, server
session id and RFC 5077 ticket from each completed handshake. A
ClientHello offers the cached id and the ticket in the session_ticket
extension (empty when there is none, so the server knows tickets are
taken); when the ServerHello echoes the id, the abbreviated flow runs
- key expansion from the cached secret, the server's ticket, CCS and
Finished, then ours - and skips everything the cache already vouched
for. A NewSessionTicket after a full handshake is stored, so servers
that only do tickets resume as well. ssl_socket_retro_was_resumed()
reports which happened, for tests.

The fetch tool takes a round count and requires every round after the
first to resume; the server matrix runs three rounds with tickets,
three with -no_ticket (session ids), and two on the ECDSA + ChaCha20
suite. Against a real host three rounds went full, resumed, resumed.
2026-09-28 22:01:18 +02:00
libretroadmin 7d579dfa3d Stage smb:// and nfs:// content for cores that read paths themselves
A core without VFS support hands the content path to its own fopen(),
where an smb:// or nfs:// URL means nothing. task_content.c now copies
such content into the cache directory first and passes the copy on,
registering it as a temporary so it goes when the content is unloaded
- the same shape the UWP fallback already takes, and the staging rule
the plan carries over from PR #19269. A core that reads through the
VFS still streams the original, which is what keeps large disc images
usable; compressed content was extracted through the VFS earlier and
is local by then. Without a cache directory the load fails with a
message saying to set one. Verified end to end: a need_fullpath core
without VFS support loads nfs:// content through the real binary and
opens the staged copy.

Two things that run found. Both network clients took their settings
in rarch_main() before the configuration file was read: the string
fields point at the live buffers so those followed, but the numeric
ones (timeouts, pool sizes, the NFS ports) were copied as zero.
retroarch_smb_init() and retroarch_nfs_init() run again right after
config_load(). And with no export configured, the nfs:// backend took
only the first path component as the export; it now mounts prefixes
shortest first until the server accepts one and remembers the split
per connection, so nfs://server/srv/nfs/roms/file works for an export
of /srv/nfs/roms.
2026-09-28 21:57:55 +02:00
libretroadmin 85f7a64bed Menu: NFS Network Settings and Browse NFS Export
Settings > Network gains an NFS Network Settings group (server,
export, sub directory, timeout, maximum connections, NFS and MOUNT
ports) and Load Content gains Browse NFS Export, which opens the
configured server as a content list the way Browse SMB Share does.
The group entry is an S_ACTION row in the netplay action definitions
so its label string is generated; the settings rows themselves are
descriptor holdouts in menu_setting.c (free-input strings, plain uint
ranges), and the list, ok, deferred-push, sublabel and title handlers
mirror the SMB ones under HAVE_NFSCLIENT. Every deliverable of the
network stack plan now has its surface in the tree. The compile
matrix gains the NFS + menu shape over the touched units.
2026-09-28 21:49:21 +02:00
libretroadmin 75afa24129 Configuration keys for the nfs:// client
nfs_server, nfs_export and nfs_subdir, plus nfs_timeout,
nfs_num_contexts and the nfs_port / nfs_mount_port pair for servers
without a portmapper, under HAVE_NFSCLIENT in the settings struct,
config.def.h and configuration.c, handed to the VFS backend from
retroarch.c beside the SMB settings at startup and released at exit.
An nfs://server/path URL then resolves against the configured export;
nfs://server/export/path keeps working with nothing configured. The
keys round-trip through retroarch.cfg. Menu rows for them follow; the
compile matrix gains configuration.c with NFS and no menu.
2026-09-28 21:36:48 +02:00
libretroadmin e4fb323d86 Add nfs:// to the VFS over the built-in NFSv3 client
vfs/vfs_implementation_nfs.c implements the nfs:// backend contract
carried over from PR #19269 (nfs_init_cfg, retro_vfs_*_nfs, the
nfs_dir_handle and nfs_dirent shapes, probe and last-error) on
net_nfs3.c, with a small pool of connections keyed by export so a
stream keeps the one it opened on. A URL carries everything a mount
needs - nfs://server/export/path - so it works with no settings at
all; the settings, when present, supply a default export and subdir
(nfs://server/path is then relative to them), the timeout, the pool
size and fixed NFS / MOUNT ports for servers without a portmapper.
vfs_implementation.c dispatches the scheme everywhere it dispatches
smb://: open / read / write / seek / tell / flush / close, stat,
opendir / readdir / dirent name, type and stat, closedir, and the
copy path's native-or-not decision.

HAVE_RETRONFS turns it on (configure: yes whenever networking is on;
the console Makefiles do not set it) and defines HAVE_NFSCLIENT for
the surface, replacing the vendored-libnfs approach of #19269. The
unfs3 matrix now also drives the real VFS - filestream and
retro_dirent over nfs:// URLs - and the compile matrix gains the
griffin HAVE_RETRONFS shape. A full RetroArch build links with it on.
Menu and configuration rows for the settings come next.
2026-09-28 21:27:47 +02:00
libretroadmin ed1043377f libretro-common: add the built-in NFSv3 client, net_nfs3.c
An NFSv3 client (RFC 1813) over ONC RPC on TCP with AUTH_UNIX, one
translation unit with a small synchronous API (rnfs_*): the portmapper
for the MOUNT and NFS ports unless given, MOUNT v3 for the export's
root handle, then paths resolved component by component with LOOKUP
and GETATTR, READ and WRITE (FILE_SYNC) chunked to 64 KiB, CREATE,
SETATTR for truncation, REMOVE, MKDIR, RENAME, and directory listing
with READDIRPLUS, falling back to READDIR plus a LOOKUP per entry on a
server that answers NOTSUPP (unfs3, some embedded ones). The XDR is
built and unpacked by hand. The socket is non-blocking and every send
and receive is bounded by the context timeout; reply records may
arrive fragmented. This is the transport for the vfs_implementation_nfs
contract carried over from PR #19269; hooking it in comes next.

samples/network/nfs_retro holds a test tool and local_unfs3_test.sh,
which builds unfs3 from its repository when no unfsd is installed and
runs the tool against it on fixed ports: mount, a 200 KB write and
read-back, seeks, stat, mkdir, rename, truncate, listing, unlink, and
a refused export. The libretro-common CI job runs it.
2026-09-28 20:46:44 +02:00
libretroadmin f3b5d1039d SMB client: share enumeration over srvsvc, bounded I/O
rsmb_enum_shares() runs NetrShareEnum level 1 through the srvsvc named
pipe on an IPC$ connection: a DCERPC bind (srvsvc 3.0 over NDR 2.0)
and one request, both built and unpacked by hand since this is the one
RPC the frontend makes. The VFS backend's share listing for smb://server
is back on the built-in client with the same filtering as before
(disk shares only, hidden, temporary and $-suffixed ones left out), so
the share browser works again.

The connection socket is now non-blocking and every send and receive
goes through the timeout variants bounded by the context timeout: a
request to a server that stops answering fails after that long instead
of holding the caller forever, which the frame loop cannot afford.

The samba matrix enumerates shares at the API level (the share in use
listed as a disk share, IPC$ typed as IPC) and through the VFS with a
hidden share in the config, and its cleanup now also stops the
samba-dcerpcd that smbd spawns per config - a stale one answers the
srvsvc pipe for nobody. The compile matrix gains configuration.c with
SMB and no menu, and with neither.
2026-09-28 20:39:14 +02:00
libretroadmin 53de1ffe59 configuration: SMB show-settings row only with a menu
settings_def_settings_show_smb.h reads DEFAULT_SETTINGS_SHOW_SMB_CLIENT,
which config.def.h defines inside HAVE_MENU, but configuration.c was
including the row under HAVE_SMBCLIENT alone. That was latent while
SMB support needed a libsmb2 the menu-less jobs did not have; with the
built-in client on by default it broke every --disable-menu build.
The five includes are now under HAVE_MENU as well, like the other
settings-visibility rows.
2026-09-28 20:33:13 +02:00
LibretroAdmin b6d67bb049 video: a frame due every other vblank is not a queued one
The threaded video's display pacing judges a frame queued behind
another - and drains a period for it - when its push-to-present
latency reaches a period and a half of the display. On a display faster
than the content that period is the wrong one: a 60 fps frame on a
120 Hz panel is due every other vblank, and a render of a few
milliseconds plus the wait to its vblank reached 12.5 ms on its own, so
every longer frame drained a queue that was never there and the core
lost a content period each time. The judgement is now against the
content's period, which the hold already computes, or the display's
where that is longer. On a display at the content's rate nothing
changes.

samples/gfx/threaded_video gains the fast-display lane: a presenter on
a 120 Hz grid with the core at 60 fps and the render alternating 2 and
7 ms must show no drains and keep the core at 60, where the display's
period drained 29 of 120 frames and ran it at 48.
2026-09-28 20:23:11 +02:00
libretroadmin 7e4ca8f2c6 Use the built-in SMB2/3 client for smb:// and SMB cloud sync
HAVE_RETROSMB puts net_smb2.c behind the VFS SMB backend and the SMB
cloud-sync driver in place of libsmb2. configure turns it on whenever
HAVE_CRYPTO is on and no libsmb2 was asked for, so main builds stop
carrying the bundled library; --enable-smbclient / --enable-libsmb
still select a libsmb2. Both consumers keep their libsmb2-shaped source
through net/net_smb2_compat.h, which presents the calls they make -
context and credential setup, connect, open / read / write / lseek /
ftruncate / close, stat / unlink / mkdir / rename, opendir / readdir /
closedir, echo, the I/O size limits, error text - over the built-in
client with the same return conventions (lseek's -EINVAL, libsmb2's
type and stat structs). net_smb2.c gains unlink, mkdir, rename (SET_INFO
FileRenameInformation) and ftruncate (FileEndOfFileInformation) for
them. A Kerberos request fails at connect so the backend's NTLMSSP
fallback runs; share enumeration (SRVSVC over IPC$) is not carried yet,
so browsing starts at a named share.

The samba matrix now also drives the real vfs_implementation_smb.c
(stat, open, seek, read, tell, opendir / readdir) over each dialect,
and the compile matrix gains the griffin HAVE_RETROSMB shape. A full
RetroArch build links with the client selected and no libsmb2 symbol.
2026-09-28 20:17:59 +02:00
libretroadmin 780a209406 tls_retro / smb_retro matrices: do not fail on the EXIT trap
Both scripts run under set -e and end with a trap that kills the
server they started; when it has already gone, kill (or pkill)
returns non-zero, the trap aborts on it and the script exits 1 after
every case has passed - which is how the libretro-common CI job failed
on a green TLS matrix. The kill in each trap is now allowed to fail.
2026-09-28 20:03:35 +02:00
libretroadmin f87f1a9eae libretro-common: add the built-in SMB2/3 client, net_smb2.c
An SMB2/3 client on the cleanroom crypto, one translation unit with a
small synchronous API (rsmb_*): connect to \\server\share over TCP
445, open / read / write / seek / close, stat, opendir / readdir /
closedir, echo. NEGOTIATE offers 2.0.2 through 3.1.1 with the 3.1.1
preauth-integrity (SHA-512 chain) and encryption-capability contexts,
GCM preferred over CCM; SESSION_SETUP carries NTLMv2 inside SPNEGO
(NEGOTIATE, CHALLENGE, AUTHENTICATE with the target info echoed and a
MIC over all three; no NTLMSSP integrity flags, so SPNEGO needs no
mechListMIC leg and no RC4); keys come from the session key through
the SP 800-108 KDF on 3.x with the preauth hash as context on 3.1.1.
Every request after setup is signed - HMAC-SHA256 on 2.x, AES-CMAC on
3.x - and replies are verified; when the server or the share requires
it every message is sealed in a TRANSFORM_HEADER with AES-CCM (3.0,
3.0.2) or AES-GCM (3.1.1). Reads and writes are chunked to the
negotiated limits (64 KiB cap); STATUS_PENDING interim replies are
skipped. Two 70 KiB buffers per context on the heap; the largest
frame is under a kilobyte.

samples/network/smb_retro holds a test tool and local_samba_test.sh,
which runs smbd on the loopback through a matrix: every dialect with
signing mandatory, CCM and GCM sealing required, and a refused logon,
each writing, reading back, seeking, stat-ing and listing a 200 KB
file. The libretro-common CI job installs samba and runs it. Hooking
the client up behind vfs_implementation_smb.h in place of libsmb2 is
the next step.
2026-09-28 19:58:58 +02:00
libretroadmin 4519d80d39 libretro-common: SMB3 and NTLMv2 crypto for the SMB client
The pieces the cleanroom SMB2/3 client needs that the stack did not
have: AES-CCM (RFC 3610, any nonce 7-13 and tag 4-16, the 11/16 shape
SMB 3.0 and 3.0.2 seal with) in crypto.c beside GCM and CMAC; and in
kdf.c the SP 800-108 counter-mode KDF over HMAC-SHA256 that SMB 3.x
derives its signing, sealing and application keys with, MD4 and
HMAC-MD5 for NTLMv2, and ntlm_ntowf_v2() computing the NTLMv2 key from
password, user and domain with the UTF-16LE and upper-casing rules of
MS-NLMP. MD4 and HMAC-MD5 exist for that one protocol and say so.

Every one is checked against published vectors in the suite: RFC 3610
packet vector 1 plus the SMB3 CCM shape, RFC 1320 A.5, RFC 2202, a
KBKDF vector cross-checked against a reference implementation, and the
NTOWFv2 vector from MS-NLMP 4.2.4 including the case rules. kdf.c now
uses utils/md5.c, so the test, bench and sample link lists carry it.
2026-09-28 19:22:01 +02:00
libretroadmin 064aa5fc08 tls_retro sample: link Winsock and fopen_utf8 on Windows
The fetch tool's Makefile now adds -lws2_32 -liphlpapi when built with
MinGW (MSYS2 or a cross gcc) and compiles compat/fopen_utf8.c, which
vfs_implementation.c calls on Windows; without them the link failed on
every net_socket.c and net_compat.c symbol. The libretro-common CI job
cross-links the tool with x86_64-w64-mingw32-gcc after running the
local server matrix, so the Windows link line stays checked on Linux.
2026-09-28 19:15:09 +02:00
libretroadmin 208e997863 TLS client: AES-256-GCM-SHA384 suites, CI lanes
The built-in client now also offers ECDHE-ECDSA and ECDHE-RSA with
AES-256-GCM, whose PRF and transcript run on SHA-384: kdf.c gains
HMAC-SHA384 (RFC 4231 vector in the suite), tls_prf() and the Finished
computation take the hash from the negotiated suite, and both digests
run over the transcript until the ServerHello picks one. That is the
full set of AEAD suites a TLS 1.2 server can require, so a server
configured for AES-256 only no longer turns us away.

The libretro-common CI job runs samples/network/tls_retro/
local_server_test.sh - the openssl s_server matrix over every suite,
both curves, RSA / P-256 / P-384 certificates and the verify-mode
refusals - and the compile matrix gains the griffin HAVE_RETROSSL
shape. A full RetroArch build links with the client selected and no
mbedtls or BearSSL symbol in the binary; the matrix also passes with
the fetch tool built under ASan and UBSan.
2026-09-28 19:11:35 +02:00
libretroadmin 73a676b4eb libretro-common: add the built-in TLS 1.2 client, net_socket_ssl_retro.c
A TLS 1.2 client behind the existing ssl_socket_* API, on the
cleanroom crypto: ECDHE over P-256 or P-384, RSA or ECDSA server
authentication through crypto/x509 against net/cacert.h, AES-128-GCM
or ChaCha20-Poly1305 records, SHA-256 transcript and PRF, SNI,
signature_algorithms and renegotiation_info sent; no resumption,
renegotiation or client certificates. The handshake runs inside
ssl_socket_connect() as the other backends' do; application reads
hand back one decrypted record at a time, and the verify-mode and
logging hooks behave as with mbedtls (required fails closed, optional
logs and continues, disabled logs once). Handshake messages accumulate
in one 64 KiB buffer that is not compacted until the handshake ends,
since the parsed Certificate points into it. x509.c exposes its ECDSA
signature decoder for the ServerKeyExchange.

HAVE_RETROSSL selects it: configure prefers it whenever HAVE_CRYPTO is
on, so main builds stop needing mbedtls or BearSSL, both of which stay
selectable under their own flags. samples/network/tls_retro carries a
fetch tool and local_server_test.sh, which runs an openssl s_server
matrix: every suite, both curves, RSA / P-256 / P-384 certificates
under a CA made on the spot, and the refusals that must happen (wrong
hostname, untrusted CA) with verify required, optional and disabled.
The tool also completes real HTTPS round trips against public hosts.
2026-09-28 18:50:45 +02:00
libretroadmin 26c4b52af8 libretro-common: add P-384 to the cleanroom public-key code
pk.c's elliptic-curve code now runs over a curve descriptor - prime,
order, b, generator, word count, and the Montgomery constants derived
from them on first use - with P-256 and P-384 as the two instances;
keygen, ECDH and ECDSA verification are exposed for both. P-256 stays
on the TLS fast path, so its word count is spelled as a constant on
that branch and the compiler keeps its unrolled Montgomery clones:
the bench numbers do not move. x509.c accepts P-384 subject keys and
ECDSA signatures over them, so every one of the 155 certificates in
net/cacert.h now loads as a trust anchor; the 15 P-384 roots (DigiCert
G3 and G5, GlobalSign E46, Amazon Root CA 3 and 4, Entrust EC1 and the
others) were being skipped before. Verified against reference P-384
ECDH and ECDSA vectors on both limb widths, the qemu-aarch64 lane, and
the shipped bundle.
2026-09-28 18:41:21 +02:00
libretroadmin 38cbed4d6c libretro-common: add X.509 certificate verification
crypto/x509.c gives the cleanroom stack the certificate layer its TLS
client needs: a DER reader (definite lengths only, non-minimal
encodings refused), x509_parse() for the fields a client acts on
(issuer, subject and its commonName, validity, SubjectPublicKeyInfo
for RSA and P-256, basicConstraints, keyUsage, subjectAltName,
signature algorithm), x509_verify_signature() over RSA PKCS#1 v1.5 and
ECDSA with SHA-256/384/512, hostname matching on dNSName entries with
a single leftmost-label wildcard (commonName only when there is no
subjectAltName), a trust store loaded from the PEM bundle in
net/cacert.h, and x509_verify_chain(): from the leaf, each step is a
certificate from the server's chain or a trust anchor whose subject
names the current issuer and whose key verifies the signature, with
validity, cA, keyCertSign and pathLenConstraint checked on the way, in
whatever order the server sent the chain. Names compare as DER octet
strings. Everything points into the caller's DER; the parsed chain is
heap-allocated so a frame never carries it.

140 of the 155 bundle certificates load as anchors; the rest carry
P-384 keys, which pk.c does not have yet, and are skipped rather than
refused so the store still loads. test_crypto.c gains a suite built
from a generated three-level chain (RSA root, P-256 intermediate with
pathLen 0, RSA leaf with wildcard SAN): signatures under each
algorithm, hostname rules, expiry, a bad signature, a rogue CA under
the pathLen-0 intermediate, an out-of-order chain with the root
included, and loading the shipped bundle. Built under HAVE_CRYPTO
beside the other crypto units.
2026-09-28 18:30:17 +02:00
libretroadmin 784067e2ff libretro-common: ARMv8 AES + PMULL path for the cleanroom crypto
crypto.c gains the ARM counterpart of its x86 AES-NI + PCLMULQDQ path:
the block cipher, CTR with four blocks in flight and GHASH on the
ARMv8 crypto extension, gated the way lrc_hash.c gates its SHA path -
used unconditionally when the baseline carries the extension, else
compiled in under target("+crypto") on toolchains whose arm_neon.h
declares the intrinsics regardless of the baseline (Clang 16+, GCC 9+)
and chosen at run time on RETRO_SIMD_AES. The GHASH multiply is the
byte-reversed-operand algorithm the x86 path uses, with the lane
shifts spelled as vext. Apple Silicon, Android, Switch and Linux ARM
builds get hardware AES-GCM; everything else keeps the round-table
cipher and 4-bit GHASH tables.

Nothing on an x86 runner executes this, so Makefile.test gains
crypto-aarch64-check: with an aarch64 cross gcc and qemu-user on PATH
the crypto suite runs under emulation in both the dispatched and the
baseline-crypto shape, built against test/check_shim.h since libcheck
is not installed for the target; without the tools it says so and
passes. The libretro-common CI job installs both and runs it.
2026-09-28 18:17:55 +02:00
libretroadmin 0de2afffd1 keychain: resolve gethostuuid at run time on macOS
The PowerPC job builds against an SDK whose unistd.h does not declare
gethostuuid(), so the Apple machine-identity branch failed to compile
under -Werror=implicit-function-declaration. The function is looked up
with dlsym() instead, with the prototype declared locally: no header
has to know it and a host without the symbol (a 10.4 libSystem) simply
takes the key-file-only key, which is what every non-Apple platform
without a machine identity does already.
2026-09-28 18:09:26 +02:00
libretroadmin 614d2fe2cd Encrypt credentials at rest in retroarch-keychain.cfg
Every sensitive value written to retroarch-keychain.cfg is now sealed:
"$kc1$" followed by base64(nonce || ciphertext || tag), ChaCha20-Poly1305
under a master key with the setting's own name as associated data, so a
blob cannot be moved from one key to another. The master key is
HKDF-SHA256(salt = retroarch-keychain.key, a 32-octet random file
created owner-only beside retroarch.cfg, ikm = the machine identity:
MachineGuid on Windows, /etc/machine-id on Linux, gethostuuid() on
macOS 10.5+, nothing elsewhere, where the key file alone is the key).
A keychain file or a copied config directory on its own no longer
carries usable secrets. Plaintext values from before this change load
as they always did and are sealed on the next save; a keychain file
whose key file is missing or belongs to another install keeps its
blobs untouched through load and save, with a warning, rather than
having them overwritten by empty values. Values are sealed and opened
through heap buffers so nothing path-sized lands on the stack. The
layer is HAVE_KEYCHAIN (default on) and needs HAVE_CRYPTO: a build
without the crypto library, such as the small consoles, keeps the file
in the clear as before. No user-facing setting, there is no trade-off
to expose.

libretro-common/file/keychain.c holds the format and key derivation
and is exercised by test/file/test_keychain.c (round trip, per-setting
binding, tamper, plaintext pass-through, key file persistence, loss and
corruption) from Makefile.test; configuration.c opens the entries after
merging the keychain file and seals them in config_save_credentials.
2026-09-28 17:50:04 +02:00
libretroadmin 4490a7e1ee libretro-common: add cleanroom crypto foundation
Adds libretro-common/crypto in three translation units, built under
HAVE_CRYPTO (qb default on; the GameCube, Wii, PS2 and other console
Makefiles do not set it, so nothing lands in their RAM). crypto.c:
AES-128/192/256 (forward cipher on round tables, CTR, GCM with 4-bit
GHASH tables, CMAC), ChaCha20, Poly1305, an AEAD front (ChaCha20-
Poly1305, AES-256-GCM), constant-time compare and clear. kdf.c:
HMAC-SHA256 over the streaming SHA-256 in lrc_hash, HKDF, PBKDF2, a
ChaCha20 fast-key-erasure DRBG and crypto_random_bytes() over the
platform entropy source (RtlGenRandom / CryptGenRandom resolved at run
time on Win32 with no header or link dependency, BCrypt on WinRT,
getrandom then /dev/urandom on POSIX, the kernel RNG on 3DS, Vita and
Switch, a clock-mixed fallback elsewhere). pk.c: fixed-width Montgomery
bignum with 64-bit limbs where the compiler has 128-bit products and
32-bit limbs otherwise, RSA PKCS#1 v1.5 verification, and P-256 ECDH,
key generation and ECDSA verification. SHA-384/512 join SHA-256 in
lrc_hash. Pure C89 with no third-party code; on x86 with GCC or Clang an
AES-NI + PCLMULQDQ path is compiled in under the target attribute and
chosen at run time, the way lrc_hash already does for SHA-NI, so HMAC,
HKDF, PBKDF2 and the DRBG inherit that SHA-NI path as well.

This is the base the keychain, TLS, and SMB signing and encryption
build on. test/crypto/test_crypto.c checks each primitive against
published vectors (FIPS 197, RFC 4493, the GCM spec cases including 8-
and 60-octet IVs, RFC 8439, RFC 4231, RFC 5869, PBKDF2, FIPS 180-4) and
the public-key side against reference-generated ones, on both the
vector and scalar AES paths and both limb widths, from Makefile.test in
the libretro-common CI job. samples/crypto/bench compares throughput
with the mbedtls the Linux build links: on an AES-NI host AES-256-GCM
runs 5.8x, ChaCha20-Poly1305 1.2x, HMAC 2.4x, P-256 ECDH 2.5x, ECDSA
verify 1.8x, and RSA verify 1.0-1.1x per fresh key. Every frame stays
under the 2 KiB libretro-common budget; RSA keeps its operands on the
heap. The compile matrix gains two griffin lanes, crypto on and off,
since griffin is where a file-local name can collide with another
unit's and only the MSVC job builds it.
2026-09-28 17:50:01 +02:00
Codex 41caa78885 Handle HTTP cache allocation and resolver startup failures
Reject cache entries whose hostname allocation fails, check DNS entry and resolver-thread creation, and roll back failed startup without leaving a pending cache entry. Complete resolver hostname-copy failures as failed lookups. Close newly created sockets when connection-pool allocation fails.

Add deterministic allocation and thread-start failure tests, cleanup checks and recovery coverage for threaded and non-threaded builds, and register both variants in CI.
2026-09-28 05:55:26 +02:00
Codex 9ada776276 Retain cached DNS addresses during HTTP connection setup
Connection setup uses addrinfo after releasing the DNS cache lock. An expiration sweep can reclaim it while a slow connection attempt still uses it. Count active connect users under the cache lock, skip their entries during eviction, and release the reference on every exit path without holding the lock across socket or TLS operations.

Add deterministic eviction tests for plain and TLS success/failure, early exits and overlapping users, and register both build variants in task CI.
2026-09-28 05:51:45 +02:00
Codex 9ce46a1c61 Rebind download widgets at extraction handoff
Copying frontend_userdata leaves the widget keyed to the finished download until extraction publishes progress. A delayed start can let the widget expire without unlinking the extraction task.

Transfer the link under the widget state lock after extraction creation succeeds, reusing the existing owner-change logic to clear terminal state and cancel expiration. Cover pending and displayed widgets, delayed progress, and teardown before the new task runs.
2026-09-28 05:47:10 +02:00
libretroadmin f8d59795c1 Remove unused functions 2026-09-28 05:44:24 +02:00
Codex 108642a968 Publish progress from the regular task runner
The regular runner executes on the pumping thread but skipped running-task progress pushes as though a separate threaded gather would deliver them. Publish updates for unfinished tasks after their handler, while leaving final delivery to retirement. This restores notifications and progress callbacks when threaded tasks are disabled.

Test intermediate updates and exactly one final update; clarify prefetch's existing callback deduplication.
2026-09-28 05:41:53 +02:00
Codex aa76e13d37 Cancel superseded widget title transitions
Rapid title updates queued multiple completion callbacks. After the first transferred msg_new into msg, a later callback could free the displayed text with no replacement. Give title transitions their own animation tag, cancel before replacing them, and cancel that tag at widget teardown.

Exercise queued callbacks and unrelated movement animations in the ownership test, and run that test in graphics CI.
2026-09-28 05:16:09 +02:00
Codex 140a30ea9f Publish suppressed task completion to attached widgets
A muted task or failed final title allocation can suppress its last progress push, leaving a widget linked to a freed task. Send an empty completion message when frontend data is attached. The runloop updates widget lifetime state without forwarding empty text to notifications or speech.

Cover muted and missing-title completions, callbacks outside the property lock, and real task retirement with an attached widget.
2026-09-28 05:12:20 +02:00
Codex 32b32a0ec2 Wire task snapshots into sample CI and widget harnesses
Classify task_queue_snapshot_test as runnable so the sample workflow executes it instead of rejecting the new target. Link the production task queue in both widget harnesses to resolve task_get_progress_snapshot, and initialize its lifecycle around the tests.
2026-09-28 04:16:15 +02:00
Codex a5fdae785b Avoid restarting unchanged widget title transitions
Compare snapshots with the displayed title when no transition is pending. Transferring the pending string on animation completion clears msg_new, so comparing only that pointer restarts identical titles on later progress pushes. Cover repeated snapshots after completion in both widget styles.
2026-09-28 04:12:22 +02:00
Codex 2828675c32 Preserve finished task state while drawing widgets
A cancelled or failed task may finish with progress between 1 and 99. Clearing its finished flag while drawing makes widget cleanup treat its retained task pointer as live after the task has retired. Keep the completion state authoritative regardless of the displayed progress.

Add a production-renderer regression covering 38 completion, cancellation, error and live-task cases across both widget styles. Include the compatibility string header required by the shared test stubs on Windows.
2026-09-28 04:04:45 +02:00
Danny KrauseandClaude Opus 4.8 71445e8e50 metal: load default.metallib by path when newDefaultLibrary returns nil
On macOS, -[MTLDevice newDefaultLibrary] only resolves default.metallib
from inside a .app bundle's resources. For a bare executable (CLI builds,
non-.app installs, a dev build run in place) it returns nil, leaving the
Metal driver with no shader library -- every -newFunctionWithName: then
returns nil.

That nil function is passed straight to
-newComputePipelineStateWithFunction:error: when building the pixel-format
conversion filters, which asserts ("computeFunction must not be nil") and
aborts the process. Depending on timing it can instead surface as a
half-initialized driver or a plain "Cannot open video driver".

Two fixes:
 - When newDefaultLibrary returns nil, fall back to loading
   default.metallib explicitly via -newLibraryWithURL:error: from the
   executable's own directory (where `make install` places it). The normal
   .app bundle path is unaffected.
 - Guard -newFunctionWithName: in +[Filter newFilterWithFunctionName:...]:
   a nil function now returns gracefully with an error log instead of
   tripping the NSAssert inside newComputePipelineStateWithFunction.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-09-28 04:07:32 +02:00
Codex f9490313cd Transfer owned task snapshot strings into widgets
Reuse snapshot strings for widget creation and title updates, and move the pending string when its animation completes. Check the one remaining creation duplicate before publishing the task association. This removes redundant allocations and avoids NULL dereferences on allocation failure.

Exercise the production message path with allocation-failure injection, ownership accounting, immediate and animated updates, terminal flags, error text and full-queue rollback.
2026-09-28 03:59:37 +02:00
Codex 9d8228ebb1 Snapshot task display properties before updating widgets
Copy title, error, flags and progress together under the task property lock. Widgets consume the owned copy after that lock is released, preserving the existing task association and widget-state locking. Keep terminal flags available on allocation failure so retiring tasks do not leave live widget references.

Add regression coverage for concurrent string replacement, retained snapshots, allocation failures and an unthreaded runner.
2026-09-28 03:35:32 +02:00
Codex 0b3c7c495e Capture autosave SRAM on the main thread
Replace concurrent live-SRAM reads and the uninitialized frame sequence with worker-requested snapshots. Preserve nested netplay scopes, service paused iterations, and keep disk I/O outside the publication lock. Retain failed writes for retry instead of advancing the saved-image baseline.

Add regression coverage for snapshot ownership during slow I/O, nested scopes, paused checks, unchanged data, write failures, compressed routing and shutdown.
2026-09-28 03:35:32 +02:00
github-actions dd7f3fa64a Fetch translations from Crowdin 2026-09-28 00:28:45 +00:00
libretroadmin ce8f93cc1c gx: bridge internal libogc's __syscalls table to modern devkitPPC
The default GameCube/Wii build links the prebuilt in-tree libogc, whose
SYS_Init installs its heap, lock, exit and time handlers into newlib's
__syscalls dispatch table -- a table devkitPPC r38 and later no longer
provide, so the EXTERNAL_LIBOGC=0 path has not linked on any current
toolchain (CI never sees it because it builds EXTERNAL_LIBOGC=1).
Defining bare storage for the symbol would link but misbehave:
libsysbase's default sbrk would place the heap by linker symbols
instead of libogc's MEM1 arena, colliding with SYS_GetArena1Lo
allocations, and malloc would run unlocked under lwp threads.

wii/libogc_syscalls_compat.c therefore defines the table with the slot
layout the prebuilt SYS_Init actually stores to (offsets 0..32,
verified against the archive's relocations) and provides strong
definitions for the per-symbol weak __syscall_* hooks that modern
libsysbase dispatches through -- sbrk, the four lock operations, the
malloc locks, exit and gettod all route to the same libogc handlers
the old toolchain reached, with the old pre-SYS_Init NULL-slot
behaviour preserved.  The file is compiled only under
EXTERNAL_LIBOGC=0, external-libogc builds are untouched, and the
default make -f Makefile.ngc links again on the CI toolchain
(devkitPPC r38, GCC 10.2.0).
2026-09-28 02:11:25 +02:00
libretroadmin e43ea7cf34 ngc: compile out features the GameCube cannot use
24 MB of MEM1 makes resident code weight count, and the dol carries all
of it, so features that cannot do their job on this hardware are
compiled out of the GameCube build: libretrodb (defaulted on by
Makefile.common; database scanning is impractical here and the explore
menu goes with it), the webp/dds thumbnail decoders, the mod/s3m/xm
tracker, 7z (LZMA dictionaries alone outsize the console's free RAM, so
7z archives could never extract anyway) and runahead (the second
savestate never fits beside a running core).

Measured with the CI toolchain (devkitPPC r38, GCC 10.2.0) in the CI
configuration, the frontend's static image shrinks from 4.43 MB to
3.95 MB -- about 490 KB of MEM1 returned to content on every core --
and the link map confirms no libretrodb, explore, webp, dds, mod
tracker, 7z or runahead code remains in the image.
2026-09-28 02:11:23 +02:00
Acts1631 9a19e7a94d netplay: validate assigned client number before use
A host can send an out-of-range client number in SYNC and later grant
play mode, causing the client to write past fixed per-client arrays.

Reject invalid assigned numbers during SYNC and require MODE/YOU
updates to name the assigned client.
2026-09-28 01:38:29 +02:00
LibretroAdmin 4ac19205a1 xmb: recover cancelled icon thumbnails during held scrolling
Fixes #19075.

Every selection-pointer change in a playlist bumps the thumbnail
generation and cancels all in-flight icon loads.  An entry whose load
had already been dispatched on the previous step is left in the
PENDING state with no task behind it: gfx_thumbnail_handle_upload
refuses to touch a thumbnail through a stale tag, so nothing ever
clears it.

The icon dispatcher in xmb_render then skips that entry on every
subsequent pass because its status is not UNKNOWN, and the recovery
in gfx_thumbnail_request_stream (gfx_thumbnail_reset_if_orphaned) is
never reached.  Holding Down for a few seconds therefore leaves a
random subset of the visible entries permanently blank until the
list is repopulated.

Call gfx_thumbnail_reset_if_orphaned before the status early-out so
orphaned entries drop back to UNKNOWN and are re-requested.
2026-09-27 22:33:02 +02:00
Rob Loach 520097d511 tools: cover sdl3_gfx overlay setters 2026-09-27 22:13:54 +02:00
Rob Loach 2d3982c990 sdl3: Mark borrowed overlay textures 2026-09-27 22:13:54 +02:00
Rob Loach c3e3a101fa sdl3: Update comments 2026-09-27 22:13:54 +02:00
Rob Loach 12ac6b8690 sdl3: Overlay load_textures and cleanups 2026-09-27 22:13:54 +02:00
Rob Loach 73159cf698 sdl3: Adapt overlay to upstream API 2026-09-27 22:13:54 +02:00
Rob Loach 93d40d3b7c sdl3: Overlay comment and check cleanup 2026-09-27 22:13:54 +02:00
Rob Loach 6806d1aa56 sdl3: Simplify overlay rendering 2026-09-27 22:13:54 +02:00
Rob Loach b185f5e73b sdl3: Trim overlay comments 2026-09-27 22:13:54 +02:00
Rob Loach d80790dd20 sdl3: Use SDL_FRect for overlay coords 2026-09-27 22:13:54 +02:00
Rob Loach 0b1609a159 sdl3: Implement overlay interface 2026-09-27 22:13:54 +02:00
keycrossandkeycross 30a56fc750 Fix resumable core CRC during restore (#19639)
Co-authored-by: keycross <[email protected]>
2026-09-27 21:56:47 +02:00
LibretroAdmin 8e4598b6e6 tasks/task_http: remove task_push_webdav_put
Uploads go through task_push_webdav_put_stream, and nothing calls the
buffered PUT any more, on either the native or the emscripten backend.
2026-09-27 21:10:48 +02:00
LibretroAdmin 6941f76379 samples/network/webdav: stub task_push_webdav_put_stream
webdav.c uploads through task_push_webdav_put_stream, so the three
harnesses that link it need that stub in place of the plain PUT one.
The re-authentication stub pulls the body from the source in send-buffer
sized pieces, so its retry check still sees the bytes that would go on
the wire.
2026-09-27 21:10:48 +02:00
LibretroAdmin c66005dbff video_thread_hw: fence a duped frame's read of the HW ring slot
A dupe (video_refresh(NULL)) from a hardware core reached the worker
with no HW slot, so the driver drew whatever it had retained from the
last real frame - the core's texture or ring copy - without the slot
being installed or its fence re-signalled afterwards. The core's wait
on that slot at its next publish covered only the first draw, not the
dupe's repeat of it.

The ring now remembers the slot the worker last claimed a real frame
from. A dupe installs and fences that slot around the driver's NULL
frame exactly as the first draw did, so the slot's fence covers every
read of it. The push side waits, bounded, before handing the core a
slot that a still-queued dupe is about to re-read, since that dupe has
not re-signalled the fence yet.

Frontend-internal; the core-facing interfaces and their versions are
unchanged.
2026-09-27 19:36:35 +02:00
LibretroAdmin 401cb3e839 video_thread_wrapper: a dupe must not replace a queued real frame
When both ring slots are claimed, the incoming push replaces the newest
frame the worker has not taken yet. A dupe (video_refresh(NULL))
carries no image, so letting it replace a real frame threw away the
only new image the core produced in that period - a 30 fps game sends
real, dupe, real, dupe - and the driver showed the frame before it for
two periods. Without the wrapper a NULL push can never lose a frame.

When the incoming push is a dupe and the slot it would take holds a
real frame, drop the dupe instead: count the miss, queue nothing, and
leave the real frame pending.
2026-09-27 19:36:32 +02:00
LibretroAdmin 89b2562e31 d3d12: fence a duped frame's read of the core's texture (hw interface v2)
A core that has nothing new sends video_refresh(NULL). The driver then
draws the core's texture from the previous frame again through
hw_direct.current, but frame_took_texture is only set inside the
handoff block that a NULL frame skips, so no hw_v2.done signal was
queued behind the dupe's command list.

wait_sync_index promises the core that the frontend is finished with
every use of the texture. With one sync index and one texture in the
non-threaded path, the core's next wait saw the previous value already
complete and started writing the texture while the dupe's draw could
still be reading it on the GPU.

Set frame_took_texture whenever the frame draws the core's texture
directly, so the done fence covers repeats as well. No interface
change: the header already promises this.
2026-09-27 19:36:28 +02:00
LibretroAdmin 290acd340b unix: default to packaged libretro data dirs under share/libretro
Distribution packages install the libretro data sets under
<prefix>/share/libretro/<name>: FreeBSD's games/retroarch-assets and
games/libretro-core-info put assets and info in
/usr/local/share/libretro/, and Debian uses /usr/share/libretro/ for the
same.  The FreeBSD port has to sed those paths into the installed
retroarch.cfg because a stock RetroArch never looks there; anyone
building from source on such a box starts with an empty ~/.config
tree and a menu without assets or core info even though both are
installed.

The Unix defaults already probe /usr/{local/,}share/{games/,}retroarch
for assets, so extend that to the share/libretro layout: when no
LIBRETRO_* environment override is set and <prefix>/share/libretro/
{assets,info,shaders,autoconfig} exists, it becomes the default for
assets_directory, libretro_info_path, video_shader_dir and
joypad_autoconfig_dir.  Only read-only data sets are affected; cores,
saves and everything RetroArch writes keep the per-user default, so
the online updater still works.  Existing configs are untouched (the
defaults only apply to keys the config does not carry).

samples/frontend/packaged_dirs/run.sh runs the built retroarch
headless with a throwaway HOME and config_save_on_exit and checks the
written config: per-user paths when /usr/local/share/libretro is
absent, the packaged paths when it is present (that lane needs root to
create the directory and skips otherwise).  It fails on the previous
platform_unix.c and passes with this one.  The FreeBSD CI job runs it
after the build.
2026-09-27 18:53:21 +02:00
LibretroAdmin 4e1eb70caa udev: finish the kqueue code path so the BSDs no longer need libepoll-shim
udev_input.c had a half-written kqueue alternative to epoll behind a
"#if 1 / TODO" that hard-selected epoll everywhere, which is why the
FreeBSD games/retroarch port has to link the udev input driver against
libepoll-shim (UDEV_LIB_DEPENDS/UDEV_CFLAGS/UDEV_LDFLAGS in the port).

Complete it: on FreeBSD, OpenBSD, NetBSD, DragonFly and macOS the evdev
fds are registered with EV_SET(EVFILT_READ, EV_ADD, udata = device)
(the placeholder referenced an undefined LISTENSOCKET), the poll uses a
zero timespec like the epoll_wait(..., 0) it mirrors (the placeholder
referenced an undefined `timeout`), and the event loop tests
filter == EVFILT_READ && !(flags & EV_ERROR) instead of the epoll-only
`events & EPOLLIN`.  kqueue is level-triggered by default so the
drain-until-EAGAIN read loop is unchanged, and closing a device fd
removes it from the kqueue just as it does from the epoll set.
NetBSD's kevent.udata is intptr_t rather than void *, hence the
UDEV_KQ_UDATA cast.

Linux still uses epoll; that object is unchanged.  The kqueue variant
was compiled with libkqueue's <sys/event.h> to catch type errors.

CI: the FreeBSD job now fails if the linked retroarch binary still
imports any epoll_* symbol.
2026-09-27 18:34:07 +02:00
LibretroAdmin 422ed61c9d freebsd: build udev, Wayland and V4L2 without the Linux compat headers
A stock FreeBSD install could compile RetroArch only after
`pkg install evdev-proto v4l_compat`, the Linux header shims:

  input/common/wayland_common.h:23: fatal error: 'linux/input.h' file not found
  camera/drivers/video4linux2.c:35: fatal error: 'linux/videodev2.h' file not found
  cores/libretro-video-processor/video_processor_v4l2.c:49: fatal error: 'linux/videodev2.h' file not found

Neither package should be needed:

- FreeBSD ships the evdev button/key codes in base as <dev/evdev/input.h>,
  and udev_input.c already includes that header on __FreeBSD__.  Do the
  same in input/common/wayland_common.h and input/drivers_joypad/udev_joypad.c,
  the two remaining unconditional <linux/input.h> users on this platform.
  Every BTN_/KEY_/EV_/ABS_/FF_/EVIOC symbol they use exists in FreeBSD
  14.3's dev/evdev/ headers.

- configure turned V4L2 on because libv4l2.pc was installed (libv4l is a
  common FFmpeg dependency), but the sources include the kernel header,
  which on FreeBSD only v4l_compat provides.  After the libv4l2 check,
  verify <linux/videodev2.h> actually compiles and disable V4L2 when it
  does not, like the EGL check already does.

CI: the FreeBSD job no longer installs evdev-proto or v4l_compat, enables
Wayland (previously disabled for exactly this reason), and asserts that
configure auto-disables V4L2 with libv4l but no headers.  v4l_compat is
then installed and only the two V4L2 objects are recompiled so that
code path stays covered without a second full build.

Reported-by: gouchi
2026-09-27 18:31:18 +02:00
LibretroAdmin b4b7710c3e metal: take the next drawable late under threaded display pacing
With the threaded presenter holding each push to the display's vblank,
the frame no longer acquires the next drawable right after presenting;
the next frame acquires its own when it first draws. Held from the
present, it sat idle for a period with one drawable on screen and one
queued, so the video thread blocked until the next vblank on every
frame - read by the pacer as a frame queued behind another, draining
every few presents and stalling the core. Without the pacer the
acquire-after-present is unchanged.

video_frame_info_t::threaded_display_pacing is set only while the
wrapper runs, so a driver can rely on it meaning the push is being
timed; its one existing reader is the wrapper itself. threaded_video
gains a lane checking the flag against the setting and the wrapper.
2026-09-27 18:28:55 +02:00
LibretroAdmin f2ee3fd988 metal: report when a drawable reached the display
get_last_present_time now answers from each drawable's presented
handler (macOS 10.15.4, iOS/tvOS 10.3, probed at runtime; the selectors
are declared locally so any SDK builds it). presentedTime is on the
host clock and is bridged to cpu_features_get_time_usec() with one
paired read in the handler, since the frontend's CLOCK_MONOTONIC counts
sleep and the host clock does not.

The threaded presenter then lays its vblank grid on the display's own
timestamps, so display pacing holds the push against real vblanks with
vsync on instead of an estimate that drifts off phase.
2026-09-27 18:09:57 +02:00
LibretroAdmin 109b98467f video_thread: lend a spare buffer when the ring is full
A core asking for GET_CURRENT_SOFTWARE_FRAMEBUFFER while one frame is
queued and the other is being rendered - the steady state under a
driver whose present blocks until vblank, as Metal's drawable acquire
and a FIFO MoltenVK swapchain do - is now lent a third buffer kept
outside the ring. The push that returns it swaps it by pointer into
whichever slot it picks, and the buffer it displaces becomes the next
spare, so the frame is still published without a copy. The spare is
allocated on the first ask that needs it; ring-slot loans are
unchanged.

threaded_video gains a lane with a driver whose present blocks past a
display period: nearly every ask must be granted, published zero-copy,
and each drawn frame must be newer than the one before.
2026-09-27 17:58:38 +02:00
LibretroAdmin 75599abff9 runloop: back off and eventually give up when the GPU device keeps being lost
A driver that lost its GPU device set VIDEO_FLAG_GPU_DEVICE_LOST and
the runloop rebuilt the video driver at once, every time. That is the
right answer to a device lost once - a TDR, a driver update - which
comes back on the first rebuild. A device that is lost again straight
after being rebuilt is a failing driver or GPU, and rebuilding on it
every frame was a busy loop of full driver reinitialisations, each of
which failed the same way.

The first loss is still rebuilt immediately. A loss following it is
retried with a doubling delay, from one second up to sixteen, and
after six in a row the rebuild is abandoned with an on-screen message
until a minute has passed. A loss a minute or more clear of the
previous one is a fresh incident and starts the count again.

Falling back to a different video driver is left as a policy choice:
this only bounds the retry loop.
2026-09-27 17:37:49 +02:00
LibretroAdmin 7ec3c021a5 net_http/webdav: stream the upload body from the file instead of reading it whole
A WebDAV upload read the complete file into a buffer, and
net_http_connection_set_content() then copied that buffer again, so
two copies of the file sat in memory for the whole transfer. Saves are
small, but a save state is not, and several uploads run at once.

net_http gains a request-body source: net_http_connection_set_content_source()
takes a Content-Length, a read callback and an optional rewind
callback. net_http_send_request() pulls the body from the source in
64 KiB runs and sends each as it arrives, so one run is all that is
ever held; a source that delivers fewer bytes than the advertised
length fails the request as a transport error would, since the header
has already promised them. When a request has to be replayed on a
fresh connection the source is rewound first; a source with no rewind,
or one whose rewind fails, is not replayed.

task_push_webdav_put_stream() wraps that for the task queue, and the
cloud-sync WebDAV driver uploads straight from the open RFILE through
filestream_read, rewinding with filestream_seek. The existing seek to
the start before the upload is kept, as the Digest-challenge retry
still reaches this path with the file at its end.

Checked with a 3 MB body PUT to a local server: the received body is
byte-identical (SHA-256) and the request completes with 201.
2026-09-27 17:31:15 +02:00
libretroadmin 3cd67707ce compat/ifaddrs.h: C linkage for C++ includers
getifaddrs and freeifaddrs are declared with C linkage under C++, so a
C++ caller resolves them against compat_ifaddrs.c rather than looking
for mangled names that nothing defines.
2026-09-27 17:29:11 +02:00
LibretroAdmin 5ec4f2abb0 memmap: memzero_pages reports a Windows recommit failure instead of faulting
On Windows, memzero_pages() decommits the range and commits it again to
get fresh zero pages. If the decommit succeeded and the recommit then
failed, control fell through to the generic memset over a range that
was no longer backed, which faults.

memzero_pages() now returns bool. On Windows, a failed recommit after
a successful decommit returns false and leaves the range alone: it is
decommitted, and the caller must treat the allocation as lost. Every
other path succeeds or falls back to memset and returns true.

There are no callers of this API yet; this settles its contract before
one arrives.
2026-09-27 17:20:34 +02:00
LibretroAdmin 1a13723fb8 net_socket_ssl_mbed: make the verification mode an atomic rather than a volatile
ssl_authmode is written from the settings thread and read on whichever
thread opens a TLS connection. It was a `volatile unsigned`, which
promises nothing about visibility between threads and is a data race
under the C memory model, whatever the hardware happens to do with an
aligned word.

It is now a retro_atomic_int_t, stored with release and loaded with
acquire. retro_atomic.h supplies a C89 fallback on the console
toolchains without <stdatomic.h>, which is why volatile was chosen
originally, so nothing is lost there. Compiles under -std=c89
-pedantic.
2026-09-27 17:20:30 +02:00
LibretroAdmin b42d47575d 2xbr: pixel-double a frame whose key buffer cannot be grown
The per-frame YUV key buffer is grown with realloc when a frame is
larger than the buffer was sized for. If that realloc fails, the key
pass was skipped but the workers still ran, indexing the keys buffer by
the new frame's width and height - a read past the end of a buffer
sized for the smaller frame.

When the buffer cannot be grown, the workers are now given a plain 2x
pixel-doubling callback for that frame instead of the edge detector,
so the output is filled sensibly and nothing is read out of bounds.
The next frame tries to grow the buffer again.

Checked with the realloc wrapped to fail: every output pixel is the
doubled source under ASan, and the ordinary path runs once the growth
succeeds.
2026-09-27 17:20:26 +02:00
LibretroAdmin 1342ec290e video_thread_wrapper: compare a pushed frame against the lent slot as integers
A core that took a framebuffer loan may push a pointer anywhere inside
the lent slot (cropping overscan by offset) or somewhere else entirely
(it rendered into its own buffer and the loan lapses). The check was
`f >= b && f < b + buffer_size` on the raw pointers, and ordering two
pointers into different objects with the relational operators is
undefined in C. It happens to work on every compiler in use, but the
ownership test should not rest on that.

Both addresses are converted to uintptr_t and the frame is accepted
when it is at or past the slot start and its offset is inside the
slot. The row clamp for the zero-copy case is reworked to compute the
rows that fit as (buffer_size - offset) / stride rather than forming
height * stride, which can wrap.
2026-09-27 17:17:01 +02:00
LibretroAdmin ce04b1f8fe d3d12: retire recording readback buffers behind their fence instead of releasing them at once
The streamed recording ring records a copy of each frame's back buffer
into one of three readback buffers, inside that frame's command list,
and signals the queue fence behind the list. d3d12_record_free() then
released all three buffers immediately - when recording stops, when the
swapchain changes size or format, and at teardown. D3D12 requires a
resource to stay alive until every command list naming it has
executed, so the ordinary stop and resize paths could free a buffer a
still-queued copy was about to write into.

d3d12_record_free() now moves the ring onto a small retired table,
tagged with the highest fence a capture into it was signalled with,
and d3d12_record_reclaim(), run once per frame, releases a generation
once GetCompletedValue() has passed its tag. A ring nothing was ever
captured into is released outright. Should the table fill under a
storm of resizes, the oldest generation is waited for with the same
bounded wait the queue drain uses. Final teardown, which already
drains the queue, releases the ring and every retired generation
through d3d12_record_teardown().

Nothing waits on the GPU in the stop or resize paths, so the stall
the ring was introduced to remove does not come back.
2026-09-27 17:16:57 +02:00
libretroadmin 651679fc20 retro_procbarrier: claim a slot before writing into it
Two walks can read the same slot as free at the same time; if both then
wrote their fields and one lost the compare-and-swap, the winner's slot
would carry the loser's count and target, its acknowledgement would go
to the wrong walk, and the winner would wait on it for good. The
compare-and-swap now comes first and the fields are written only into a
slot the walk owns. Nothing reads them before the signal that is sent
after them, and a signal from an earlier holder cannot match the new
generation.
2026-09-27 16:23:14 +02:00
libretroadmin 53383bcfbb memmap: memjit_alloc returns NULL where there is no mmap
DJGPP ships a sys/mman.h with the protection bits and mprotect and no
mmap, so HAVE_MMAN is defined on DOS while MAP_ANONYMOUS is not.
memjit_alloc and memjit_free now take the same gate as every other mmap
user in this file -- HAVE_MMAN, not Emscripten, and MAP_ANONYMOUS
defined -- and on DOS, the consoles and Emscripten memjit_alloc is a
plain NULL, as its documentation says.
2026-09-27 16:23:10 +02:00
libretroadmin e84ba6b2f9 retro_procbarrier: stop waiting on a thread that blocks the signal after being picked
A thread can block the signal between the walk's look at its mask and
the signal's delivery, and then hold it queued for as long as it likes;
the walk now closes such a slot at its recheck instead of waiting. For
that to be safe the slot must outlive the signal, which may arrive
whenever the thread unblocks: slots move from the walker's stack to a
static pool of 128, each claimed under a generation, and the queued
signal carries index and generation rather than an address. One
compare-and-swap in the handler both checks that the slot is still the
signal's and closes it, so a delivery that comes after the slot was
closed, released or claimed again is ignored, and the pointer to the
walker's count is only ever followed while the walker still waits on it.

retro_procbarrier_test gains a check with a busy thread flipping its
mask between all-blocked and open every few microseconds while barriers
run; thousands of its signals are delivered after their slot was closed,
and none may be waited on or land anywhere.
2026-09-27 06:35:50 +02:00
libretroadmin c421134213 retro_procbarrier: identify a signalled thread by tid and start time
A tid is reused as soon as its thread is gone, so a walker rechecking a
target it is still owed cannot take the tid's existence as the thread
still being there: with threads starting and exiting around it, a later
thread under the same number would keep the walk waiting on a signal
the kernel discarded. The walk now records each target's start time
from the stat line it already reads to test for runnable, and at the
recheck closes a slot whose thread is gone or whose start time no longer
matches.

The recheck sleep starts at 250 us and doubles to 10 ms, so a walk that
caught a thread in its last microseconds is over in a fraction of a
millisecond, while one owed by a thread that is merely slow to be
scheduled costs a syscall every 10 ms.
2026-09-27 06:17:36 +02:00
libretroadmin a07fb4756f memmap: add memjit_alloc/memjit_free and memzero_pages
memjit_alloc gives a mapping the caller both writes code into and
executes: PAGE_EXECUTE_READWRITE on Windows, an executable anonymous
mapping elsewhere, and on macOS on Apple Silicon the MAP_JIT kind that
memjit_write_begin/end already serve, under the same TARGET_OS_OSX
test so iOS and tvOS keep the plain mapping. It returns NULL where no
such mapping exists, and memjit_free releases it.

memzero_pages zeroes a page-aligned range as cheaply as the system
guarantees: MADV_DONTNEED where that zero-fills (Linux, Android),
decommit and recommit on Windows, fresh pages mapped over the range
where the advice would only be advice (Darwin, the BSDs), and memset
where nothing else exists. memdecommit's documentation now says the
same about what a non-strict range reads back as.
2026-09-27 06:08:18 +02:00
libretroadmin 15aebc2a3b retro_procbarrier: don't probe membarrier on Android below API 29
Android's seccomp filter answers a syscall outside its allowlist with
SIGSYS, which ends the process, and membarrier is on that allowlist
from Android 10 (API 29) on: on Android 8.0 through 9 the probe's query
alone would kill the process at its first asymmetric eventcount. The
API level is read from ro.build.version.sdk and, below 29 or unreadable,
the probe is not made, so those systems resolve to the signal tier,
whose rt_tgsigqueueinfo, futex and tgkill are allowed there.
2026-09-27 05:39:38 +02:00
libretroadmin 3c57861087 retro_procbarrier: skip threads that block the signal on Linux
A thread with the barrier's signal blocked would never acknowledge it,
and a GL or Vulkan driver blocks every signal in each worker it creates,
so a walk that picked one waited on it for good. The walk now reads
SigBlk from /proc/self/task/<tid>/status, in small pieces so the frame
stays within budget, and passes such a thread by. That is sound under
the contract the header now states: a thread that notifies an
asymmetric eventcount must not block the signal, and every thread
started through rthreads inherits an unblocked mask.

retro_procbarrier_test gains a check that runs barriers past a busy
thread with every signal blocked. Under ThreadSanitizer walks run one
at a time, spinning for the turn rather than blocking: the tool keeps
one deferred signal per number per thread and drops a second, and runs
a deferred handler only once the thread is back in instrumented code.
With that, the signal tier completes under the tool.
2026-09-27 05:39:34 +02:00
libretroadmin a8ffa9929b retro_procbarrier: don't wait on a thread that exits before answering
A thread the Linux signal tier picks can exit before its handler runs,
and a signal queued to an exiting thread is discarded, so nothing would
ever answer for it. Each target now gets a slot of its own on the
walker's stack, carried by the queued signal: the handler closes it, or
the walker does when tgkill(pid, tid, 0) reports ESRCH, and a CAS on the
slot makes sure only one of them counts it. The walker still sleeps on
the futex and is woken by every acknowledgement; the wait times out
after 10 ms only so that a target that died is noticed. Targets are
walked in batches of 32 slots to keep the frame bounded.

retro_procbarrier_test gains a check that runs barriers while two
threads keep starting and joining short-lived threads, which stalled
the signal tier every time; the concurrency check only hit it by chance
on SMP runners. Under ThreadSanitizer the slot handoff is annotated
with __tsan_release/__tsan_acquire, since the syscall that orders it is
invisible to the tool.
2026-09-27 05:16:54 +02:00
LibretroAdmin 5ba409d23a audio: oss and audioio open the path of a list entry; audioio lists its nodes
oss lists its nodes with the card's name after the path - "/dev/dsp0
(HDA Intel)" - so the user can tell them apart. The menu stores the
entry as shown, and it comes back to init as the device string; init
opened that string as a path and failed. A device picked from the
list could not be used on OSS4 and the BSDs, where SNDCTL_AUDIOINFO
is there to name the card.

audio_device_label_path() takes the path off the front of an entry -
everything before the first " (" - and leaves a bare path alone; oss
and audioio open what it gives.

audioio listed nothing, the one driver left whose kernel offers a
choice of nodes without a list to choose from. It now walks
/dev/audio and /dev/audio0..15 as oss walks /dev/dsp*, with the name
AUDIO_GETDEV gives after the path. That needs no driver instance,
which is what the menu has when init failed and the user needs to
pick another device.

samples/audio/device_label_path compiles both drivers whole with
open() redirected to a recorder and checks each reaches it with the
path of an entry, a bare path as it is, and the default with none;
and pins the helper on its own. It fails on the previous oss and
audioio at "init opens the path of a list entry" and passes now.
2026-09-27 05:08:48 +02:00
libretroadmin 12ab7d8e89 retro_procbarrier: make every tier safe for concurrent callers
Any number of threads can be inside retro_procbarrier() at once, and no
caller waits for another. On Darwin the walk takes each running thread's
state with thread_get_state, which holds the thread, waits in thread_stop
until it is off its CPU and releases it before the call returns, so two
callers stopping each other can never be left parked on each other's
hold; KERN_ABORTED is retried. The Linux signal tier queues a real-time
signal per thread with rt_tgsigqueueinfo carrying the walk's own
acknowledgement counter, which the walker sleeps on with a futex, and
init probes that path on the calling thread and refuses non-real-time
signal numbers. The page flip claims one of four locked pages with a CAS
and flips a page of its own when all are busy.

retro_procbarrier_test gains a concurrency check: four threads issuing
barriers against each other under a watchdog, run for every tier by the
existing CI lanes. The header documents the reentrancy guarantee and
the Linux tier's real-time signal requirement. Pre-10.11 Darwin SDKs
spell MAP_ANONYMOUS as MAP_ANON, which the page flip now accepts.
2026-09-27 03:51:39 +02:00
LibretroAdmin 745a261d75 (GX) Keep the display un-blanked across video driver teardown so libogc's exception screen is visible
Every GameCube crash during content load is reported as "black screen,
then the console reboots" (RetroArch #19637 and friends) and nobody
can supply the register dump / stack trace we ask for. Reason: the
video driver is freed and re-initialised around each content load,
gx_free() called VIDEO_SetBlack(true), and libogc's default exception
handler only swaps VI to its own console framebuffer; it never clears
the black flag. So a fault in retro_load_game or driver re-init prints
its registers and stack trace to a display nobody can see, then the
__exception_setreload(8) countdown resets the console.

gx_free() now clears XFB 0 to black, points VI at it and leaves the
display enabled instead of blanking it. Looks identical to the user;
lets the exception screen show through.

For that to be safe VI must keep a valid framebuffer after the driver
instance is gone, so the two XFBs move from per-instance allocations
to process-lifetime statics (g_framebuf), allocated once in
setup_video_mode and never freed. They were already fixed at the
largest mode's size, so nothing changes per mode; it also removes a
1.4 MB memalign/free pair from every content load on a 24 MB box.

If gx_free() runs before any XFB exists (early init failure) it
blanks VI as before.
2026-09-27 03:12:19 +02:00
LibretroAdmin a47d76335f menu: only show Send Frame Luminance on Wayland builds
video_hdr_send_luminance is read solely by the Wayland context driver,
yet the HDR settings list showed it on every platform once HDR was on.
Gate the entry on HAVE_WAYLAND so Windows/macOS/X11-only builds don't
expose a toggle that does nothing there.
2026-09-27 02:45:07 +02:00
github-actions 8e35f7df88 Fetch translations from Crowdin 2026-09-27 00:28:38 +00:00
LibretroAdmin 55830573e9 rthreads: don't include pthread_np.h on PS4/Orbis
The PS4 toolchain defines __FreeBSD__ but Sony's libc has no
pthread_np.h, so the include added for FreeBSD's pthread_set_name_np()
broke the Orbis build.  Exclude __ORBIS__/ORBIS, as retro_procbarrier.c
already does for the same reason.
2026-09-27 01:50:54 +02:00
LibretroAdmin ed889cbee5 rthreads: fix FreeBSD build of retro_procbarrier and rthreads
retro_procbarrier.c gated <unistd.h>/<sys/mman.h> on PB_FREEBSD_MEMBARRIER,
but that macro is only defined further down the file (after the
FreeBSD version check), and the x86 page-flip tier uses mmap()/sysconf()
on plain PB_FREEBSD regardless.  So on FreeBSD the headers were never
included and the file failed with undeclared PROT_*/MAP_*/_SC_* symbols.
Gate the POSIX headers on PB_FREEBSD like the code that needs them.

rthreads.c calls pthread_set_name_np() on FreeBSD/OpenBSD/DragonFly,
which those systems declare in <pthread_np.h>, not <pthread.h>; include
it so the call is no longer an implicit declaration.

Found by the CI FreeBSD job.
2026-09-27 01:20:17 +02:00
libretroadmin 1de81209fb audio: name the wrapped driver in the start/stop log lines
audio_driver_start() and audio_driver_stop() log the driver through
audio_driver_get_ident(), which resolves through the threaded
pipeline's wrapper, so the lines name the device driver in use
("alsa", "wasapi", ...) rather than "audio-thread". The buffer
report in audio_driver_init_internal() uses the same accessor in
place of its own copy of that lookup.
2026-09-27 01:11:27 +02:00
LibretroAdmin d52fecafc8 menu: show sublabels for Send Frame Luminance, Peak Brightness, Use Display Peak
The three HDR settings added to settings_def_video_hdr.h ship a
sublabel string, but menu_cbs_sublabel.c never mapped their labels to
it, so the menu showed nothing under them. Add the missing rows to the
sublabel table next to the other HDR entries.
2026-09-27 01:07:39 +02:00
LibretroAdmin 961da8b1a6 (GX/GameCube) Fix content load exiting to a reboot after switching core DOLs (#19637)
Reporter (SD2SP2 + Swiss): "lighter" cores like Nestopia or SNES9x crash
on the first content load but work after the console restarts; PrBoom
and ECWolf go to a black screen and the GameCube reboots.

The attached log is not a crash. It is a clean retroarch_main_quit():
two "Saving salamander config" lines, "Saved config", "Unloading core",
performance counters. RetroArch chose to exit.

Root cause, in three parts:

1) retroarch_main_init() reads retroarch-salamander.cfg and sets
   RARCH_PATH_CORE from its libretro_path *before*
   frontend_gx_process_args() runs, and process_args only fills in
   argv[0] when RARCH_PATH_CORE is still empty. The salamander config
   names whichever core DOL ran last, so launching a different core
   DOL from Swiss leaves RARCH_PATH_CORE pointing at the wrong core.

2) On static builds every content load asks RARCH_CTL_IS_CORE_LOADED
   whether the chosen core matches the loaded one. With the stale
   path it does not, so the load goes down the "fork a new instance"
   path (command_event_cmd_exec -> frontend_driver_set_fork).

3) GameCube has no fork/exec support (set_fork is NULL, exec is
   HW_RVL-only). command_event_cmd_exec ignored the false return and
   still reported success, so the caller set RARCH_CTL_SET_SHUTDOWN
   and the frontend exited; with nothing to return to, libogc resets
   the console. That quit rewrote the salamander config with the
   correct core, which is why the *second* boot of the same DOL
   loads content in-process and works.

Fixes:

* platform_gx.c: on HW_DOL always take the core path from argv[0].
  The core linked into the DOL the loader started is the only core
  this process can run, so argv[0] is authoritative over the
  salamander config. Wii keeps the fill-if-empty behaviour.

* task_content.c: command_event_cmd_exec() now returns false when
  the frontend cannot fork, logging which core was requested and
  pushing MSG_FAILED_TO_LOAD_CONTENT; the two menu callers that
  unconditionally issued CMD_EVENT_QUIT afterwards return to the
  menu instead of shutting down. A core mismatch on a no-fork
  platform is now an on-screen error, not a reboot.

* task_content.c: "Always reload core on run content" needs fork
  support; on frontends without it the running core is all there
  is, so load in-process when it matches instead of exiting.

Host syntax-checked task_content.c with HAVE_DYNAMIC/HAVE_DYLIB
undefined and platform_gx.c with -DHW_DOL against the vendored
libogc headers. No devkitPPC on this box.
2026-09-27 01:07:34 +02:00
LibretroAdmin 52db9d0a2d camera/pipewire: rename static pipewire_* functions out of libpipewire's namespace
FreeBSD's PipeWire 1.6.9 headers declare pipewire_init(int *, char ***),
so the camera driver's static pipewire_init() fails to compile there:
"static declaration of 'pipewire_init' follows non-static declaration".
This is what the games/retroarch port's patch-camera_drivers_pipewire.c
worked around.

Rename the driver's five static entry points to pwire_camera_* so they
cannot collide with anything libpipewire exports, matching the pwire_
prefix the audio driver already uses.  No behaviour change.
2026-09-27 00:49:15 +02:00
LibretroAdmin 4cab0b8781 ci: add a FreeBSD build job
GitHub has no FreeBSD runners, so run the build inside a FreeBSD 14.3 VM
via vmactions/freebsd-vm with the games/retroarch port's default feature
set minus Qt and Wayland.  The job first checks that --disable-pipewire
leaves HAVE_PIPEWIRE_STABLE off with libpipewire installed, then builds
with udev (libudev-devd), X11, SDL2, FFmpeg, PipeWire, ALSA, PulseAudio
and OSS forced on so a missing dependency fails loudly.
2026-09-27 00:33:11 +02:00
LibretroAdmin 16e21db836 qb: do not probe PIPEWIRE_STABLE when PipeWire is disabled
HAVE_PIPEWIRE_STABLE only qualifies HAVE_PIPEWIRE (it gates the camera
driver), but it was probed independently, so --disable-pipewire on a box
with libpipewire installed still ended up with HAVE_PIPEWIRE_STABLE=1 in
config.mk and config.h.  Tie it to HAVE_PIPEWIRE: when PipeWire is off,
record PIPEWIRE_STABLE as off without probing.

While here, make griffin's include of the camera driver require
HAVE_PIPEWIRE_STABLE like Makefile.common and camera_driver.c already do,
so a griffin build against pre-1.0 PipeWire does not try to compile it.

Reported via the FreeBSD ports patch-qb_config.libs.sh ("Fix build if
multimedia/pipewire installed").
2026-09-27 00:33:07 +02:00
LibretroAdmin 023f1e8b05 udev: build the udev input driver on FreeBSD without linux_common
linux_common.o is only compiled on Linux, but udev_input.c called its
illuminance-sensor helpers unconditionally, so --enable-udev on FreeBSD
(libudev-devd) failed to link.  The sensor reads sysfs IIO nodes, which
only exist on Linux, so keep it Linux-only: the udev driver on FreeBSD
now reports the illuminance sensor as unavailable and everything else
works as before.

This is the last of the source patches the FreeBSD games/retroarch port
still had to carry; the ALSA errno.h and FFmpeg 7.1+ ones were already
fixed in master, and the camera pipewire_init clash went away when the
audio driver was renamed pwire_init.
2026-09-27 00:33:03 +02:00
LibretroAdmin 6b6b8eb597 image_blit_bands: pool the groups, retire queued runners in the caller
image_blit_bands() ran a decoder's frame conversion as row bands on a
thread pool and malloc'd a group - the bands and their counters - for
every blit, freed by whichever holder let go last. That is one heap
round trip per decoded video frame, on the frame's path (the audit's
"multi-band image conversion allocates a coordination group per
blit").

Groups now come from a static pool of eight, taken by CAS and given
back when the last holder lets go, so a runner that starts late is as
safe as it was with the heap; the heap is the fallback when every
slot is taken. And the caller, once its bands are done, runs the
runners it posted that are still sitting in the pool's queue
(tpool_help) rather than leaving them: each finds no band left and
lets go. Before, a caller faster than the pool left thousands of no-op
runners queued behind it, each holding a group - which is also why the
pool alone was not enough.

image_blit_bands_test (new, wired into Linux-libretro-common-samples)
wraps malloc through the linker and checks: every row covered once for
1080 and 719 rows at align 2, 500 blits with zero mallocs, four
callers blitting at once, and the no-pool path. The old code mallocs
once per blit and fails the count lane.
2026-09-27 00:27:01 +02:00
LibretroAdmin a2b99b09b5 build: link video_mode_select on every Linux build, not just Wayland
06aed75 made mutter_displayconfig.c call video_mode_list_finish but
only added gfx/common/video_mode_select.o under HAVE_WAYLAND, so every
Linux build without Wayland (the plain Linux job and linux-c89) failed
at link time with an undefined reference from
mutter_displayconfig_get_resolution_list.

video_mode_select.c is plain C with no platform dependency, so it goes
in the base object list next to video_driver.o, and the same include
moves next to video_driver.c in griffin.
2026-09-27 00:03:16 +02:00
LibretroAdmin 7c8e7b4230 d3d11/d3d12: stream the recording readback through a ring
The D3D recorders read the frame back synchronously, every frame:
re-render it (video_driver_cached_frame), create a fresh staging
texture (D3D11) or readback buffer (D3D12), copy the back buffer into
it, then Map with a blocking read - D3D12 drained the whole queue
first. The frame loop stalled for the GPU on each recorded frame,
which is the audit's "generic recording fallback can synchronously
serialize GPU readback". GL and Vulkan already stream theirs.

Both drivers now do what gl2's PBO ring does:

- While recording, each frame's render queues a copy of the back
  buffer into the next slot of a 3-deep ring, right after the render
  and before Present. D3D11 uses staging textures and CopyResource;
  D3D12 records the copy into the frame's own command list and
  signals a fence value behind it.
- read_viewport() takes the slot copied three frames ago: D3D11 maps
  with D3D11_MAP_FLAG_DO_NOT_WAIT and declines the frame on
  DXGI_ERROR_WAS_STILL_DRAWING; D3D12 checks the slot's fence against
  the queue and declines until it has passed. The recorder already
  treats a declined frame as "try again next frame". No re-render, no
  drain, no per-frame allocation, no wait.
- The ring is (re)created when the swapchain changes size or format,
  and freed when recording stops or the driver goes down.

HDR swapchains are decoded on the CPU from the ring copy, as the GPU
tonemap pass renders from the live back buffer and waits on it;
screenshots keep the GPU path.

The recorded frame is three frames behind, as with gl2's ring.
Compile-checked under MinGW; needs a real D3D11/D3D12 recording run.
2026-09-26 23:57:46 +02:00
libretroadmin 06aed75951 video: one place decides which mode a size and rate name
Mutter, KWin and wlroots each carried their own copy of the same two
decisions - the listed mode a requested size and rate mean, and the
order a resolution list is shown in. gfx/common/video_mode_select.c
holds them once and all three call it, reading their own mode
structures through a callback rather than copying them.

The rules are the ones Mutter already applied and the two Wayland
backends lacked: a rate further off than half a hertz is not the mode
the caller asked for unless it carries that whole-hertz label, a
progressive mode comes before an interlaced one, and the mode in use
wins a tie. samples/gfx/video_mode_select checks each of them.
2026-09-26 23:37:01 +02:00
LibretroAdmin f5503fb604 get_list_refresh_flags_test: park the worker so the HTTP task is freed ahead of the updater
The two unthreaded parity lanes relied on the unthreaded gather
reversing the running list every pass, which e8c5c81 removed: the
list keeps its order now, the HTTP task always ticks ahead of the
updater and is still allocated when the updater sees it finished.
Built against the old handler under ASan those lanes pass 9/9, so
they guard nothing.

The window that remains is the threaded one: the queue frees the
HTTP task on the main thread right after its callback, and with a
single worker the free can only land between the HTTP task's last
tick and the updater's next one. The test hits it deterministically
instead of by timing. task_set_flags() is wrapped at link time; when
the HTTP task marks itself finished, on the worker, the wrapper
pushes the updater's 'when' two seconds out. The worker only looks
at the front of the running list, so it sleeps; the main thread
checks until nothing but the updater is findable, i.e. the queue has
freed the HTTP task; then the updater ticks holding a freed pointer.
'when' is written and read on the worker thread, so there is no
race, and TSan agrees.

Against the old handler ASan reports heap-use-after-free in
task_get_flags from task_core_updater_get_list_handler on every run;
against the fixed handler the lane passes plain, ASan+UBSan and
TSan. One plain unthreaded lane stays so the direct-read path runs
under the sanitizers too.
2026-09-26 23:24:01 +02:00
LibretroAdmin b1365e86cd task_core_updater: read sub-tasks directly on the unthreaded queue
core_updater_sub_task_running() reads the sub-task through
task_queue_find() so the threaded queue cannot free it underneath
the worker. On the unthreaded queue that read is a no-op: the
gather detaches the running list before it ticks anything, and
retro_task_regular_find() only walks tasks_running, so from inside
a handler no sub-task is ever findable and the download and
decompress progress never reached the updater's progress bar.

The unthreaded queue has not needed find() since e8c5c81: it runs
handlers in list order (a sub-task pushed from the updater's tick
always sits ahead of it) and only retires finished tasks after the
pass. So when the updater reads a sub-task it has already ticked,
alive, at worst finished but not yet freed; by the next pass its
callback has set the complete flag and the updater stops looking.
Read flags and progress directly there.
2026-09-26 23:24:01 +02:00
Artem Lytkin 74f6867163 task_core_updater: don't read HTTP/decompress tasks after the queue has freed them
The list and download handlers polled their sub-tasks with
task_get_flags()/task_get_progress() and only then checked the
*_complete flag the sub-task's callback sets. The queue frees a task
right after that callback, so a tick that runs after the free still
dereferences the stale pointer. This happens in CORE_UPDATER_LIST_WAIT,
CORE_UPDATER_DOWNLOAD_WAIT_TRANSFER and, on decompress_task,
CORE_UPDATER_DOWNLOAD_WAIT_DECOMPRESS. In the unthreaded queue each
gather reverses the running list, so the updater runs ahead of its
sub-task every other pass. In the threaded queue the window is
narrower but still open.

Moving the complete check first is not enough on its own: the worker
can read 0, the gather can then free the task, and the read that
follows still hits freed memory. The flags and progress are now read
through task_queue_find(), which scans under the queue locks, and a
task only leaves the retiring list under those locks before it is
freed. The complete flag is still checked first, so a reused address
costs one wrong progress value at most. The *_task_finished bools
had no other use and are gone.

get_list_refresh_flags_test gains two unthreaded lanes. They hold the
loopback response until a gather pass of each parity, so one of them
always frees the HTTP task ahead of an updater tick. The ASan build
reports that read as heap-use-after-free on the old handler.

Fixes #19633, reported by milnet01.
2026-09-26 23:24:01 +02:00
libretroadmin f70cb54dfb video: offer only the GPUs there are, and say when one cannot present
The GPU Index entry lists the devices the running driver found rather
than a fixed sixteen: an index past them names no device, and choosing
one leaves the frontend rendering on a GPU it cannot display from, with
nothing on screen to say why.

Where that has already happened - a GPU that answers that it can
present, on a compositor that then cannot import its buffers - Vulkan
makes swapchain after swapchain and throws each away. It now says so
once, naming the GPU the index picked and where to set it back, instead
of looping in silence.
2026-09-26 23:06:23 +02:00
LibretroAdmin 9ed0db58c3 video_thread: accept a lent frame at any offset and pitch in the slot
The zero-copy lend only counted when the core pushed exactly the
pointer it was given. A core that renders its whole surface into the
loan and then crops by pointer offset - beetle-psx trims overscan that
way, with its own stride - pushed a pointer past the slot's start,
the loan lapsed, and every frame was copied as if it had never asked.

A pushed frame that lies anywhere inside the lent slot is now
published from there, with the pitch the core pushed; the slot
records the offset and the worker presents from it. Rows past the
slot are clamped as before, now counting from the offset. A push
outside the slot still lapses the loan.

threaded_video_test: the harness core gains a mode that renders the
loan and pushes a cropped window of it (8 pixels in, 4 rows down, the
loan's pitch); the zero-copy lane runs in both modes. The cropped
lane publishes 0 of 59 grants zero-copy on the old wrapper and 39,
the same as the plain lane, on this one.
2026-09-26 23:03:35 +02:00
libretroadmin 0e7a56f171 video: follow the GPU an index was chosen as, not the position
A GPU index is a position in the list its driver enumerates, which a
driver update, a BIOS change or another card reorders; the saved index
then names a different GPU, and the frontend renders on a device that
may not be able to reach the display at all. Every index - Vulkan,
OpenGL, Direct3D 10, 11 and 12, and Metal - now remembers the device it
was chosen as: where that device has moved in the list its new position
is used, where it is gone the driver's first device is used with a
warning, and index 0 means the driver's own choice as it always did.
The name is kept up to date with what was resolved, and an index set by
hand takes the device at that position and remembers it.
2026-09-26 22:44:35 +02:00
LibretroAdmin 82c06f7d17 task_queue: init(false) clears the threaded flag
task_queue_init() set task_threaded_enable only when asked for a
threaded queue and never cleared it, so init(false) after a threaded
session left the flag on and the next task_queue_check(), seeing the
flag and an unthreaded queue disagree, re-initialised the threaded
one behind the caller's back. RetroArch never hit it because its
settings path clears the flag through task_queue_unset_threaded()
first; any other user of the queue did.

The flag now follows the argument both ways. task_queue_budget_test
drops its workaround and checks that init(false) after a threaded
session stays unthreaded across a check; that lane fails on the old
queue, and the two unthreaded lanes after it fail with it.
2026-09-26 22:35:25 +02:00
LibretroAdmin e8c5c813ef task_queue: budget retirement and unthreaded handlers per check
Two audit P1s, both about task_queue_check() taking as much of the
frame as it likes on the thread that drives the frame loop:

- Retirement had no bound. Finished tasks retire (progress push,
  callback, cleanup, free) on the checking thread, and a burst of
  completions - a thumbnail scan, a bulk download - retired in one
  check however long the callbacks took.
- On the unthreaded queue every running task's handler ran once per
  check, so the frame cost grew with the running list.

task_queue_set_budget(retire_usec, retire_max, handler_usec) bounds
one check. Retirement stops once retire_max tasks or retire_usec have
gone by, after at least one; what is left stays on the retiring list,
findable, for the next check (the threaded early-out is told there is
work pending). Handlers stop once handler_usec has gone by, after at
least one due handler; the tasks not run go back to the front of the
running list, the ones that ran behind them, so the list rotates
instead of starving its tail. The unthreaded gather now keeps FIFO
order rather than reversing the list each pass, which the rotation
needs. Zero means unbounded and is the default, so nothing changes
for a caller that does not opt in.

RetroArch sets 2 ms or 32 callbacks for retirement and 4 ms of
handler time per check, leaving most of a 16.7 ms frame to the core.
task_queue_wait() loops on the check, so a blocking wait is slowed,
never changed.

task_queue_budget_test covers the count and time retire budgets and
the unbounded case on the threaded queue, and the handler budget,
rotation fairness and the unbounded case on the unthreaded one. Four
of its seven lanes fail against the old queue. Wired into
Linux-libretro-common-samples.
2026-09-26 22:33:49 +02:00
LibretroAdmin 4d7d2b0652 cpu_class: keep cpu_class_by_value under the 2 KiB thread-stack budget
The clock/capacity table was unsigned long[256] (2 KiB on 64-bit)
beside a 512-byte path, a 2704-byte frame that the stack-budget
check refuses for the 8 KiB thread stacks on PSP and GX.

32-bit values are enough (kHz clocks stay below 10^7, capacities at
1024) and the path buffer is 256 bytes; the frame is now about 1.3
KiB. No behaviour change; fast_cores_test still passes.
2026-09-26 22:32:25 +02:00
LibretroAdmin 2a4bd2162f features_cpu: rank the bigger last-level cache first within a class
The two CCDs of an X3D part are one performance class and within a
few percent of one clock, so cpu_features_get_processor_order() put
the frequency CCD first and a core pinning its emulation thread to
"the strongest processor" landed on the die without the V-cache.

Within a class the ranking now prefers the larger last-level cache,
then the higher clock. On Linux that is cache/index3/size per
processor (96M behind the V-cache CCD, 32M behind the other); on
Windows the level-3 RelationCache record whose mask covers the
processor, from the same GetLogicalProcessorInformation call the
core list already comes from. Parts with one L3 size are unaffected.

fast_cores_test: the X3D fixture with and without the cache figures,
and confined to the frequency CCD.
2026-09-26 22:08:13 +02:00
LibretroAdmin f7d228d77c features_cpu: rank processors by class, within the affinity mask
cpu_features_get_processor_order() is what a core uses to pin its
threads to the strongest processors (lrps2 pins EE, VU and GS to the
first three). It ranked by raw cpuinfo_max_freq on Linux and not at
all on Windows, and it ignored the process affinity mask. rthreads'
fast-core classification had just been fixed to use the kernel's own
P/E topology; this brings the ranking onto the same source.

- The per-processor class read moves into cpu_class.h, a private
  header included by both features_cpu.c and rthreads.c (neither may
  link the other: rthreads.c is built alone by the sample harnesses,
  features_cpu.c by the cores). Linux: cpu_core/cpu_atom, then
  cpu_capacity within 70%, then max clock within 85%. Windows 10
  1607+: CPU Sets EfficiencyClass per processor.
- The ranking sorts by class first, then clock, then SMT sibling,
  then id. An E-core can no longer outrank a P-core on clock, and on
  Windows a hybrid part is now ranked by class instead of by
  enumeration order.
- Processors outside the calling thread's affinity mask are left out
  of the order: a pin from a parent process, a container or the user
  is a boundary, not a target.
- The Linux gather ranks every processor the kernel publishes rather
  than the first N by id, where N was the thread count.

fast_cores_test now also drives the ranking through the fixture tree:
Intel hybrid (P, P siblings, then E; confined to E), ARM three-tier,
x86 favoured cores (lead the order, no class of their own; confined
to four), and an X3D part (clock decides within one class).
2026-09-26 21:51:01 +02:00
LibretroAdmin df96c5f5d6 video_filter: size the Automatic worker count from spare cores
RARCH_SOFTFILTER_THREADS_AUTO gave a filter one worker per logical
CPU. That is wrong twice over: the pool competes with the emulation,
video, audio and task threads for the cores those need, and the light
filters get slower as workers are added (EPX at 256x224 measured ~26%
slower on eight workers than on one; LQ2x and 2xSaI lose their
four-worker gain at eight). A filter sits on the frame's critical
path - every worker must finish before the frame presents - so a few
unhindered workers beat many contended ones.

Automatic now resolves as:
- physical cores the process may use (sthread_get_core_topology, or
  cpu_features_get_core_amount_physical where there is no topology),
- minus the reserved frame-critical threads: the emulation thread,
  plus the video, audio and task threads where each runs on its own
  thread (video_driver_init_filter tells the filter the count),
- minus one of headroom for the GPU driver and the OS once above two,
- capped at RARCH_SOFTFILTER_AUTO_MAX (8),
- then capped by the plugin's workload: light filters (epx, lq2x,
  crop_borders, darken) run alone, medium ones (2xsai, super2xsai,
  supereagle, phosphor2x) stop at four, heavy ones (ntsc, ntsc_crt,
  2xbr, blargg_ntsc_snes, and any plugin not in the table) take the
  budget.

On a 4-core with everything threaded that is one worker; on a
16-core X3D with everything threaded, 8 for the heavy filters, 4 for
the medium, 1 for the light. An explicit Video Filter Threads value
is passed through untouched, as before.

The arithmetic is rarch_softfilter_auto_budget(); filter_lifecycle
checks it across eleven machine/reserve/plugin combinations and that
the live answer stays within bounds.
2026-09-26 21:34:09 +02:00
LibretroAdmin 050d0a92c2 Place the video thread and task worker on the fast cores too
"Prefer Performance Cores" (thread_prefer_fast_cores) pinned only the
main and audio threads. The two other frame-critical threads never
asked: the threaded video worker (video_thread_wrapper.c) and the task
queue worker (task_queue.c), which runs the completion handlers the
frame loop retires.

Both now take the same sthread_prefer_fast_cores() placement when the
setting is on:

- video_thread_set_prefer_fast_cores() is set from video_driver.c
  before video_init_thread() and applied at the top of
  video_thread_loop().
- task_queue_set_prefer_fast_cores() is set in retroarch.c before
  task_queue_init() and applied at the top of threaded_worker(). It
  is a setter rather than a task_queue_init() argument so the samples
  and stubs that call task_queue_init() are untouched. No effect on
  GCD, which has no worker thread.

The setting's sublabel now names all four threads.
2026-09-26 21:20:09 +02:00
LibretroAdmin fae92c6116 rthreads: add sthread_get_core_topology()
Counts the physical cores the calling thread may run on, split into
fast and slow by the same classification sthread_prefer_fast_cores()
pins by. SMT siblings count once. On a homogeneous part every core is
fast and slow is 0.

This is what a worker pool needs to size itself: how many cores are
left once the frame-critical threads have theirs, and on a mixed part
whether growing further means spreading onto the slow cluster.
cpu_features_get_core_amount() gives the thread count and knows
nothing about classes.

Sources:
- Linux/Android: the process affinity mask, thread_siblings_list for
  the core grouping, and the class from the topology read (cpu_core/
  cpu_atom, cpu_capacity, or clocks within the band).
- Windows: CPU Sets, one core per (Group, CoreIndex), fast when its
  EfficiencyClass is the highest present. Before 1607,
  GetLogicalProcessorInformation core count, all fast.
- Apple: hw.perflevel0/1.physicalcpu on Apple silicon; hw.physicalcpu
  otherwise, all fast.
- Elsewhere: returns false and the caller falls back to the thread
  count.

The Linux class read is split out of the classifier so both share it.
fast_cores_test gains topology cases: Intel hybrid with SMT, the same
confined to either cluster, ARM three-tier, an X3D 16c/32t part and
an affinity of six threads on it, and an empty sysfs.
2026-09-26 21:15:45 +02:00
LibretroAdmin 2ffc7931d9 rthreads: classify fast cores from the kernel's topology, not exact clocks
sthread_prefer_fast_cores() on Linux took as "fast" exactly the CPUs
whose cpuinfo_max_freq equalled the highest in the system. That is
only right when the fast and slow clusters differ by a lot, as on
classic ARM big.LITTLE. On x86 it goes wrong:

- Favoured cores (Intel Turbo Boost Max 3.0, AMD CPPC preferred core)
  report a few percent more than their siblings, so the fast set
  shrank to those two cores and the main and audio threads were both
  pinned onto them.
- On an X3D part the V-cache CCD boosts lower than the other, so the
  emulation thread was pinned to the frequency CCD - the opposite of
  what a game wants.

The classifier now takes its answer from, in order:
1. the kernel's own hybrid classification (/sys/devices/cpu_core and
   cpu_atom on Intel hybrid parts);
2. cpu_capacity on ARM, keeping every core within 70% of the best so
   a three-tier part (prime + big + little) keeps its big cores;
3. cpuinfo_max_freq, within 85% of the highest, so favoured cores and
   X3D CCDs are one class while a real slow cluster (20-40% lower)
   still splits off.

The classification is split from the affinity read so it can be
driven from a fixture sysfs tree. fast_cores_test covers the three
sources, the affinity-already-inside cases and the two x86 regressions;
against the old logic it fails the favoured-core, X3D and three-tier
ARM cases. Wired into Linux-libretro-common-samples.
2026-09-26 21:10:52 +02:00
Rob LoachandLibretroAdmin c8a2d80e5f SDL3: Add Sensor Support (#19455)
* Add SDL3 device sensor support

* Gate sensors to port 0, zero on failure

* sdl3: Sensor comments

* sdl3: simplify sensor input

* sdl3: Fix comments

* sdl3: Fix comment

* SDL3: Log sensors on joypad connect

* SDL3: Restore sensor state on reconnect

* SDL3: Track sensor state in pad struct

* sdl3: Update comment

* input: route sensor disable requests to both the joypad and input drivers

input_driver_set_sensor stops at the first driver that reports the
action as handled. That is right for an enable, where the sensor ends
up held by exactly one driver, but wrong for a disable: joypad drivers
are documented to report the disable of a sensor they never had as a
success, so when the host sensor is actually held open by the input
driver (e.g. the SDL3 input driver's accelerometer/gyro fallback for a
pad with no sensors), the disable never reaches it. The sensor stays
open, keeps being polled, and keeps feeding the core after it asked
for it to stop.

Send disable actions to both drivers. Disabling a sensor a driver does
not hold is already defined as a no-op success, so this is safe for
every existing driver pair.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-09-26 21:12:29 +02:00
LibretroAdmin 02ec762c0c menu: show physical CPU cores in System Information
The "CPU Cores" row printed cpu_features_get_core_amount(), which is
the logical processor count, so a 16-core/32-thread 9950X3D read as
"CPU Cores: 32".

It now prints the physical count from
cpu_features_get_core_amount_physical(), and adds the thread count
when SMT makes it larger: "CPU Cores: 16 (32 threads)". Parts without
SMT keep the plain "CPU Cores: 4" form.
2026-09-26 20:58:12 +02:00
Rob Loach f5919416a9 SDL3: Add Pen Support (#19488)
* Add pen support to SDL3 input driver

* sdl3: Report pen as pointer input only

* Skip pen density lookup out of range

* Cache SDL_Window in sdl3_input_t

* sdl3: Update pen comments

* sdl3: infer pen proximity from events

* sdl3: Unneeded comment

* sdl3: map pen barrel buttons to mouse
2026-09-26 20:45:48 +02:00
LibretroAdmin 93c5294ceb tools: an extern "C" definition in a C++ file is checked against its prototype
uwp_main.cpp defines several win32_common.h functions under extern
"C" and does not include the header, so when a prototype changes no
compiler compares the two: the C callers are updated and pass, the
C++ body keeps its old parameters and links, and the mismatch is only
found at run time. That is how 0b2613d crashed Xbox on boot, and how
win32_set_video_mode was left taking four parameters against a
three-parameter prototype.

tools/extern_c_prototype_check.py reads the tree as text: every
function defined inside an extern "C" region of a .cpp or .mm file
is looked up in the .h files, and its parameter list is compared with
each prototype's, type by type with the names stripped. A definition
with no prototype anywhere is not judged. --selftest runs it over
three fixtures: a match, the 0b2613d parameter-count drift, and a
parameter-type drift. The new workflow runs both the self-test and the
tree check on every push and pull request.
2026-09-26 19:24:35 +02:00
LibretroAdmin 276ea271cc uwp: win32_set_video_mode takes its size as one word
Same drift as win32_check_window: the header and every caller
(uwp_egl_ctx, d3d11, d3d12) moved to (data, dims, fullscreen) when
the size became one word, and the UWP definition in uwp_main.cpp,
which never sees the header, kept (data, width, height, fullscreen).
Under the C calling convention it took the packed word as width, the
fullscreen flag as height, and read fullscreen from a register no
caller set - so the launch view size and windowing mode UWP asks for
were nonsense. Found by tools/extern_c_prototype_check.py.

The body unpacks the word with VIDEO_SCALE_W/H; nothing else changes.
2026-09-26 19:24:32 +02:00
LibretroAdmin de109de02a uwp: win32_check_window hands its size back as one word
dc44cc7b0a changed win32_check_window to take one unsigned* for the
size, and every caller passes one. uwp_main.cpp carries the UWP
definition of that function, and it does not include win32_common.h,
so the compiler never saw the prototype change: it kept taking width
and height as two pointers. The callers pass four arguments, the
body reads a fifth that was never passed and writes through it, and
on Xbox it writes both axes on every call - RetroArch crashes at the
first alive() check on boot. Bisected by Nejsplitter to 0b2613d, the
first build after that change to get through the MSVC lane.

Both paths now pack width and height with VIDEO_SCALE_PACK and hand
the word back, matching the prototype and win32_common.c.
2026-09-26 19:24:28 +02:00
LibretroAdmin 15eddd52b9 Video: add Video Filter Threads setting
CPU video filters always ran on one worker per detected core
(RARCH_SOFTFILTER_THREADS_AUTO). That suits the heavy filters (NTSC,
NTSC CRT, 2xBR) but over-parallelises the light ones: at 256x224 EPX
is ~26% slower on eight workers than on one, and LQ2x/2xSaI lose
their four-worker gain at eight.

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

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

tools/settings_table.reference is regenerated; it had drifted on
master (HDR, custom viewport and window size rows) independently of
this change.
2026-09-26 18:52:29 +02:00
LibretroAdmin 3c1b3a5f5e net_http: move the body-framing check out of cloud_sync_driver
cloud_sync_http_body_is_framed lived in network/cloud_sync_driver.c,
which the webdav_options, webdav_reauth and gdrive_backup samples do
not link, so all three failed with an undefined reference once
webdav.c and google_drive.c started calling it (#19631).

The check restates the two tests net_http itself uses to pick the body
framing, so it belongs beside them: it is now net_http_body_is_framed
in libretro-common/net/net_http.c, which every one of those samples
already links. cloud_sync_driver.c/h lose the copy and its includes.

webdav_reauth's stale-nonce test answered the retried GET with a 200
that carried no headers, which the read callback now correctly refuses
as unframed; the test's response gets a Content-Length like a real one.
All three samples build and pass under ASan/UBSan again.
2026-09-26 18:50:15 +02:00
LibretroAdmin b9119c03ff net_socket_ssl.h: include net_compat.h for ssize_t
The header declares ssize_t-returning functions but only pulled in
stdlib.h, boolean.h and retro_common_api.h, so it compiled only when
the includer had already brought net_compat.h in. network/tls_log.c
includes it first, and the 3DS and Vita builds fail there with
"unknown type name ssize_t". Include net_compat.h, as net_socket.h
does for the same declarations.
2026-09-26 18:50:11 +02:00
LibretroAdmin 8251314c43 tls: point at the system clock when a certificate fails on its dates
Under the default Required policy a device with an unset or badly
wrong clock rejects every server's certificate as expired or not yet
valid. The mbedTLS verify text alone reads like a server-side problem,
so log an explicit hint that the device's date and time are the likely
cause, and name the setting that restores connectivity meanwhile.
2026-09-26 18:33:36 +02:00
Anthony SchemelandClaude Opus 5.5 ba4c8b0798 net/tls: verify server certificates by default (#19626)
The mbedTLS backend set MBEDTLS_SSL_VERIFY_OPTIONAL on every connection
and ignored the verification result. A handshake therefore
succeeded against any certificate, including self-signed and expired
ones, so anyone able to intercept traffic could impersonate the
servers used by the online updater, RetroAchievements and cloud sync.

Add a tls_verify_mode setting (Required, Optional, Disabled) with
Required as the default:

- Required: the handshake fails on a bad certificate, the connection is
  refused and the reason is logged.
- Optional: the previous behaviour, now with a logged warning.
- Disabled: no verification, with a warning on every connection.

The mode is applied at startup and whenever the setting changes, and
takes effect on the next connection. Logging goes through two hooks
that the backend defines as weak no-ops, so libretro-common still
builds on its own; network/tls_log.c routes them into the RetroArch
log. Griffin builds skip the weak definitions, since tls_log.c lands in
the same translation unit. BearSSL already verifies and fails closed,
so it only gains a no-op setter.

Tested on Linux with the bundled mbedTLS: full build, and the touched
files also compile with C89_BUILD=1. A small program linked against
the built objects connected successfully to buildbot.libretro.com,
thumbnails.libretro.com, retroachievements.org and the Google APIs
hosts in Required mode, and refused self-signed.badssl.com and
expired.badssl.com with the reason logged. Optional and Disabled
connected to the self-signed host and logged their warnings.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-09-26 18:36:20 +02:00
Acts1631 7a39d86aa4 netplay: bound remote savestate size (#19614)
LOAD_SAVESTATE carries a peer-controlled output size. The client used it
to grow and zero every rollback buffer before checking decompression,
letting a remote host request excessive memory with a small packet.

Bound output size to the fixed per-session zbuffer limit before any
rollback buffer is resized.
2026-09-26 18:02:23 +02:00
LibretroAdmin 6de51c466c Revert "config: create retroarch.cfg owner-readable only"
This reverts commit e4ed924601.

The rationale was stale: credentials are no longer written to
retroarch.cfg. config_save_file strips every sensitive key into
retroarch-keychain.cfg, and config_save_credentials already calls
path_set_private() on that file after writing it. The chmod on the
main config protected nothing.
2026-09-26 17:56:52 +02:00
LibretroAdmin e4ed924601 config: create retroarch.cfg owner-readable only
The main config stores plaintext credentials (WebDAV, Google Drive and
streaming tokens, netplay passwords). With the usual umask a freshly
written file is world-readable. When config_save_file creates the file
it now chmods it to 0600 on POSIX platforms.

A config that already exists is left with the mode it has: users share
configs across accounts and read them from scripts, so changing the
mode of an existing file on every save would break those setups.
2026-09-26 17:54:56 +02:00
LibretroAdmin 788be8dbae cheevos: use memmove when dropping a URL parameter
rcheevos_filter_url_param removes a parameter by copying the tail of
the URL over it with strcpy. Source and destination are overlapping
ranges of the same buffer, which strcpy does not define; memmove does.
2026-09-26 17:54:56 +02:00
LibretroAdmin fa49a43dc2 command: bound READ_CORE_RAM and WRITE_CORE_RAM to the memory region
rcheevos_patch_address returns a pointer with no length, so
READ_CORE_RAM served whatever byte count the request asked for and
WRITE_CORE_RAM wrote every byte in the payload, both running past the
end of the core's memory region. READ_CORE_RAM also computed its reply
buffer as `40 + nbytes * 3` in unsigned int: a request for
~1431655766 bytes wrapped that to a few bytes and the reply loop then
wrote 4 GiB through it.

Add rcheevos_patch_address_avail, a thin wrapper over
rc_libretro_memory_find_avail that also reports how many bytes remain
in the region. READ_CORE_RAM clamps nbytes to that, WRITE_CORE_RAM
stops there and logs that the rest of the payload was dropped. Both
READ_CORE_RAM and READ_CORE_MEMORY additionally refuse requests above
16 KiB: the reply is 3 characters per byte in one UDP datagram, so
larger requests could never be delivered anyway, and the cap keeps the
size arithmetic well clear of the wrap. READ_CORE_MEMORY already
clamped to the descriptor length.

Also size the " -1" reply against the allocation instead of
sizeof(reply), which was the size of the pointer.
2026-09-26 17:54:56 +02:00
LibretroAdmin b5172edaa1 command: add network_cmd_bind_address
The network command interface has always bound to every interface, so
with network_cmd_enable on, any host that can reach the port can send
LOAD_CORE, WRITE_CORE_RAM or WRITE_CORE_MEMORY. Many setups rely on
exactly that (phone remotes, cabinet front-ends, home automation), so
the default cannot change without breaking them.

Add network_cmd_bind_address. Empty, the default, keeps the historical
all-interfaces bind; "127.0.0.1" restricts the interface to the local
machine. command_network_new takes the address and passes it to
socket_init, which only sets AI_PASSIVE for a NULL server, so an
explicit address binds to that interface alone. The startup log now
names what the socket is bound to.
2026-09-26 17:54:56 +02:00
Anthony SchemelandClaude Opus 5.5 4ee62a575f security: validate replay, UPnP and cloud sync input (#19628)
Three places trust data that comes from outside RetroArch:

- replay_set_serialized_data reads the embedded replay length from the
  save state. A negative value, or one shorter than the replay header,
  was passed on to the seek and write that restore the replay. Refuse
  such states.
- natt_parse_desc_node walks the IGD description XML a router serves.
  On a node without children it recursed with a NULL child and then
  read child->next, so a leaf node or an empty root crashed it. It
  also recursed only into childless nodes, so a <service> nested in
  <root>/<device>/<serviceList>, the normal UPnP shape, was never
  found. It now always recurses into children and checks the
  serviceType and controlURL text before using it.
- task_cloud_sync_fetch_server_file took the local path from a
  server-supplied manifest key as strchr(key, '/') + 1. A key without
  '/' read from address 1, and a key containing ".." or an absolute
  path was joined onto the sync directory, so a hostile server could
  write outside it. Such keys are now refused and counted as a
  failure.

The description walk moves to network/natt_desc.c and the key check
to tasks/task_cloudsync_path.c, so the new sample tests
(samples/tasks/natt_desc, and cloudsync_path_safety_test beside the
existing cloudsync test) compile the shipped code directly. Both are
added to Linux-samples-tasks.yml.

Tested on Linux: full build; touched files also compile with
C89_BUILD=1 without warnings. The workflow's commands for both new
steps pass, and the existing cloudsync test still passes under
SANITIZER=address. With the old parser or the old key handling
substituted, each test segfaults.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-09-26 17:22:37 +02:00
Anthony SchemelandClaude Opus 5.5 891d09385c cloud_sync: refuse unframed download bodies, handle allocation failure (#19631)
net_http fails a transfer that ends before its Content-Length or its
final chunk. A response with neither is delimited by the connection
closing, and net_http documents that it cannot tell a complete body
from one cut off by a dropped connection. The WebDAV and Google Drive
read callbacks accepted such a body as a successful download and wrote
it over the local save. A connection that drops mid-download could
therefore replace a save with a truncated copy, which the next sync
then uploads.

The read callbacks now treat a 2xx response without Content-Length or
"Transfer-Encoding: chunked" as a failed download and leave the local
file alone. The check is shared as cloud_sync_http_body_is_framed and
matches the headers net_http itself uses to pick the body framing.

Also NULL-check allocations that were used unchecked: the WebDAV
digest-auth chain (HA1, HA2, response, header), webdav_delete's
callback state, and the Google Drive context in
gdrive_begin_with_token. Each now fails the operation instead of
writing through NULL.

Tested on Linux: full build; touched files also compile with
C89_BUILD=1 without warnings. A small program linked against the
built cloud_sync_driver.o returned true for Content-Length and for
chunked (any case), false for no framing, "gzip, chunked" and a NULL
list. Not tested against a live WebDAV or Google Drive server.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-09-26 17:18:17 +02:00
LibretroAdmin ff1d2de796 wayland: NULL-check the drag offer before writing to it
wl_data_device_handle_drop set offer_data->dropped before checking
whether offer_data was NULL, so the check never protected anything.
Move the assignment after the check.
2026-09-26 16:28:34 +02:00
LibretroAdmin fee695d45c statestream: insert the real index on hash collision
uint32s_index_insert_exact declared a shadowing 'uint32_t idx = 0' in
the RHMAP_HAS branch, so every object whose hash collided with an
existing bucket was recorded in that bucket as index 0 instead of its
own index. Lookups through the bucket then compared against the
all-zeros pattern rather than the new object, and pop/remove could
never find the entry to take it back out. Use the caller's idx.
2026-09-26 16:28:34 +02:00
Anthony SchemelandClaude Opus 5.5 67c857e163 misc: fix crashes and leaks in wayland, drm, replay index and drivers (#19630)
Small, independent fixes for crashes and leaks on reachable paths:

- wayland: gfx_ctx_wl_get_video_size_common dereferenced a NULL output
  when no window output was set and all_outputs was empty. The DnD
  drop handler leaked the received buffer when fmemopen failed, and
  always leaked the line getline allocated.
- drm: drm_plane_setup logged a NULL drmModeGetPlaneResources result
  and then dereferenced it. init_drm read `connector` uninitialised
  when the card reported no connectors.
- uint32s_index (replay statestream): uint32s_bucket_free left the
  freed pointer and length in the bucket. RHMAP_CLEAR keeps value
  slots, so a later RHMAP_PTR on the same slot could see them and free
  the vector again. uint32s_index_pop now also refuses an empty index
  and checks RHMAP_HAS before RHMAP_PTR, which would otherwise create
  a bucket for a missing hash.
- ozone: ozone_context_reset_horizontal_list allocated a node for an
  entry without one but never stored it in the list, so it leaked.
- discord: discord_json_next_strdup leaked the previous string when a
  key repeated.
- core_backup: core_backup_add_entry leaked an empty filename.
- audioworklet, caca, sixel, vga: the init functions leaked their
  struct when the second allocation or the font renderer failed.

Tested on Linux: full build. The touched Linux files show no new
warnings; the C89 build of the Wayland and DRM files fails on this
host in system headers and existing lines, with no diagnostic on a
changed line. drm_gfx.c was compiled although HAVE_PLAIN_DRM is off.
samples/tasks/core_backup passes. The audioworklet, caca, sixel and
vga drivers are not buildable here and were not compiled.

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-09-26 16:18:37 +02:00
LibretroAdmin ffa4ec336a ps3: add a PS3 display server, switch resolutions through it
Screen resolution on PS3 lived in the ps3 context driver: a list of
resolution ids it probed for at init (leaked on every reinit - the
malloc'd list was never freed), stepped through with left/right on
the menu entry and applied with OK by reinitialising the driver. On
PSL1GHT that did nothing at all: the RSX driver configures the video
output itself, from the mode the system menu has it in, and never
looked at the chosen id.

dispserv_ps3 is now the display server for __PS3__. It lists the
modes the connected display takes (480, 576, 960x1080, 720, the
1080-line scalers, 1080), marks the one in use, and switches between
them. The menu entry now opens the same resolution dropdown the
desktop platforms use, and Start resets to the system menu's mode as
it does elsewhere. A choice is stored in current_resolution_id - the
video output resolution id, as before, so existing configs keep
their modes - and the video driver is reinitialised to configure the
output in it; picking the mode already in use reinitialises nothing.
A refresh-rate-only set_resolution (the autoswitch) is refused.

ps3_display_server_resolution() is what configures the output: the
chosen mode when the display takes it, otherwise the system menu's.
The RSX driver's rsx_init_screen now uses it in place of the system
mode, which makes the setting work on PSL1GHT; the PSGL context uses
it for its device size in place of the unchecked stored id, so a
choice the current display cannot show (a config moved to another
TV) falls back instead of failing.

The mode table and those rules are in dispserv_ps3_modes.c, which
takes the display's answers through a callback and so has no SDK
dependency. The context driver loses its list and its output
size/prev/next callbacks, and global_t loses the list, count, check,
idx and initial fields nothing reads any more.

Test: samples/gfx/dispserv_ps3 links the real server and mode rules
against stubbed PSL1GHT video output queries (declared in the tree's
PS3 stub <sysutil/video.h>, now carrying PSL1GHT's layouts) for
displays taking different sets of modes with the system menu in each:
the list is exactly what the display takes with the mode in use
marked; each entry, through the label the menu prints and the parse
the menu does, selects its mode, stores its id, reinitialises once
(never for the mode in use) and reports its rate and size; the mode
the driver configures follows the choice and falls back to the system
mode for one the display lacks or an unknown id; refresh-only and
unavailable modes are refused without touching anything. Wired into
Linux-samples-gfx (ASan + UBSan); compile-matrix gains a PSL1GHT
syntax lane for the server.
2026-09-26 04:45:01 +02:00
LibretroAdmin 8e386c7642 gx: add a GameCube/Wii display server, switch resolutions through it
Screen resolution on GameCube/Wii was a private mechanism of the gx
video driver: a fixed table in gx_gfx.c, stepped through with
left/right on the menu entry and applied with OK, with no idea of
what each mode would actually run at. Issue #19380 is a user on a
240p CRT working out modes and aspect by trial and error.

dispserv_gx is now the display server for GEKKO. It lists the modes
the console's TV standard can show, each with the refresh rate and
scan type it really runs at (640x240 at 59.826 Hz, 640x480 at
59.94 Hz interlaced, 50.0801/50 Hz on PAL, progressive on a
component cable with 480p on), and switches between them. PAL's own
preferred mode (640x574) is in no fixed entry and is listed as the
default. The menu entry now opens the same resolution dropdown the
desktop platforms use. A choice is stored in current_resolution_id
- the table keeps its order, so existing configs keep their modes -
and handed to the driver, which still programs the VI since it owns
the framebuffers and the menu surface a switch has to redo. A
refresh-rate-only set_resolution (the autoswitch) is refused: the VI
cannot change rate without changing mode.

The mode table and the rules for what a mode becomes (double strike
at or below half the standard's lines, interlaced or progressive
above, clamping, field rate) move to dispserv_gx_modes.c, which has
no libogc dependency. gx_set_video_mode and the server's list both
use it, and the TV standard detection is shared, so the list cannot
promise a mode the driver programs differently.

Fixes a bug in the resolution dropdown on every platform along the
way: action_cb_push_dropdown_item_resolution parsed the label with
strtoul() inside VIDEO_SCALE_PUT_W/H, whose VIDEO_SCALE_PACK
evaluates its arguments twice. The height's second strtoul() started
where the first had left 'end', returned 0, and every mode picked
from the list went out as Wx0. Each axis is now parsed into a local
first.

Test: samples/gfx/dispserv_gx links the real server and mode rules
against stubbed libogc queries and runs NTSC, NTSC 480p, PAL 50,
EuRGB60 and MPAL: every listed mode fits the standard, exactly one
is current, and each entry - through the label the menu prints and
the parse the menu does - selects that mode, stores its id and
reports its listed rate; refresh-only and unknown modes are refused
without touching anything; config ids keep their meaning. It failed
on every selection against the old dropdown parse. Wired into
Linux-samples-gfx (ASan + UBSan) with a grep guard against the
double-evaluation shape; compile-matrix gains Wii and GameCube
syntax lanes for the server.
2026-09-26 04:21:55 +02:00
LibretroAdmin 8dc24ef3e5 menu: return to Load Core when backing out of Download a Core
The core updater list's cancel handler flushed the menu stack back to
'Online Updater'. When the list was opened from 'Load Core' there is no
'Online Updater' entry on the stack, so the flush popped all the way to
the Main Menu.

The list only ever occupies one stack level, so use the default pop
instead - it already handles removing core updater search terms and
returns to whichever menu the list was opened from.

Fixes #19625
2026-09-26 03:34:21 +02:00
LibretroAdmin 4be3eba587 midi: keep the driver usable after a failed lazy open
midi_driver_open() called midi_driver_free() when the device failed to
open, which also dropped the device lists and cleared
rarch_midi_drv_inited. After that the MIDI menu lists came back empty
and the next GET_MIDI_INTERFACE could not retry until a driver reinit.

Split the device teardown out into midi_driver_close() and use that on
the failure path: the driver stays selected, the enumerated lists stay
valid for the menu, and the next request from the core retries the
open. midi_driver_free() still does the full teardown for driver_uninit.

Also make midi_driver_set_input()/set_output() a quiet no-op while no
device is open: with lazy open the menu can change the device before
any core has asked for MIDI, and the new value is picked up when the
device is opened, so this is not the error the DEBUG log claimed.
2026-09-26 03:22:09 +02:00
Artem Lytkin fa876218d1 midi: open MIDI devices only when a core requests the interface
drivers_init enumerated and opened the MIDI devices on every start,
whether or not a core would ever use them. With the default WinMM
driver and "Microsoft GS Wavetable Synth" output that means a MIDI
stream for every Windows user, and on Windows 11 the first WinMM MIDI
call after boot starts the Windows MIDI Service and blocks until it is
up. That is the slow first launch in #19247.

Keep driver selection in drivers_init, and enumerate and open the
devices when a core calls GET_MIDI_INTERFACE. The request survives
driver reinits (SET_SYSTEM_AV_INFO, audio latency changes) and is
dropped when the core is unloaded. The menu device lists are filled
on demand without opening a device.

Frame Delay no longer relies on the MIDI stream holding the timer
period down since 5c30a403be, and the WASAPI workaround for it was
removed in 8d4bccac55.

Fixes #19247
2026-09-26 03:22:09 +02:00
Artem Lytkin cb8a7083fb companion: smooth thumbnails when upscaling (#19621) 2026-09-26 03:02:35 +02:00
github-actions 4afe682ee1 Fetch translations from Crowdin 2026-09-26 00:26:05 +00:00
LibretroAdmin 3df09555d4 (PSP/Vita/PS4) A window handed to the device is its until the next call returns
Vita nightlies since the audio rework crackle in PCSX, UAE4ARM, blueMSX,
VICE, Fuse and more (#19624). The worker released a window to the writer
as soon as the output call that handed it over returned, on the reading
that the call returns once the device has taken the window. It does not.
sceAudioOutOutput / sceAudioSRCOutputBlocking wait for the *previous*
window to finish, queue this one and return with it still in flight: the
SDKs document passing NULL as the way to wait for that last window, and
the PSP driver, traced on hardware for PPSSPP (docs/sceAudio.md), arms a
descriptor pointing at the caller's memory, waits for one descriptor to
retire and returns - nothing is copied, the DMA reads the window out of
the ring over the next period. A write landing in it in that period is
heard.

The old driver had the same early release, one call earlier still, but
an 8192-frame ring that rate control kept about 85 ms from the edge, so
the writer never reached the window in flight. The reworked ring is the
latency setting - five periods at 64 ms - and the threaded pipeline
keeps it full, so the writer sits on the freshly released period every
period and lands in audio being played.

The worker now keeps its own cursor for the next window to hand over, and
publishes read_pos one window behind it: after each call returns, the
window that call handed over is the device's and everything before it is
the writer's. The window in flight is therefore held inside the ring, so
the ring is sized to the setting directly with no device period beside
it, buffer_size() is the ring, and the floor goes to six periods - five
of room, which still holds a 30 Hz video frame of audio beside the
window in flight. Same fix on all three drivers; they share the code and
the semantics.

samples/audio/psp_ring's mock device now reads a window across the period
after the call that queued it, the way the hardware does, rather than
inside the call. On the previous drivers every lane fails with a break in
the middle of a window - frame 384 of 512, the writer's overwrite - and
the old unified psp_audio.c fails the same way under a flat-out writer.
The harness also gains the check target the unclaimed-samples job looks
for, since without one it was never being run.
2026-09-26 02:10:24 +02:00
LibretroAdmin c33b44231e replay: play back a zero-frame recording, halt a pending one, add init/shutdown regression test (#19622)
Follow-up to 368b842. Two more ways the replay state machine could
get stuck, both found by the test added here, plus the test.

1. A recording halted before its first frame produced a file with a
   header and a checkpoint and nothing after it. The player rejected
   it: bsv_movie_reset_playback() reads the first frame's events at
   init, hit EOF, raised BSV_FLAG_MOVIE_END and failed - the same path
   the truncated-file deadlock came from, now landing as "failed to
   load movie file" for a file the recorder itself wrote. Treat it as
   what it is, a replay of zero frames: leave the checkpoint pending
   at init and let the first frame restore it and hit EOF, which ends
   playback through the normal end-of-replay path.

2. movie_stop_record() only looked at the promoted handle. The record
   task installs the handle as next_handle and the run loop promotes
   it on the next frame, so halting from the menu before a frame ran
   (core paused) found no handle, returned false and left the
   RECORDING flag set and the pending handle allocated: the session
   was stuck "recording" and every later Record/Play Replay refused
   until restart. Finish a pending recording like any other; it is a
   recording of zero frames.

samples/tasks/bsv_replay_init: links the shipping tasks/task_movie.c
and input/bsv/bsvmovie.c against a stub core through the real task
queue, intfstream and VFS, in the iOS configuration (HAVE_RZSTD on,
HAVE_STATESTREAM off) the issue was filed against, and mirrors the
run loop's two-line replay protocol. Lanes: record/play round trip
(raw and zstd, core restored to the checkpoint), truncated first
checkpoint (start fails, user told, no MOVIE_END left, nothing pauses,
Close Content clears), zero-frame replay (plays and ends on frame one),
stray MOVIE_END (movie_stop clears it). Against b40db22 the truncated
lane reproduces the report exactly - paused after every frame, carried
across Close Content - and the empty lane fails on both bugs above.
Clean under ASan+UBSan with leak detection. Wired into
Linux-samples-tasks.yml next to bsv_replay_bounds_test.

Not changed: retro_vfs_file_truncate_impl calls ftruncate() with no
fflush() first. The recorder truncates every frame, so this was a
suspect for the reporter's short file, but the preceding
fseek(0, SEEK_CUR) flushes on glibc and BSD libc alike and the round
trip lane cannot make it lose a byte. The origin of the short file on
iOS is still unconfirmed; the new OSD message and the [Replay] log
lines will name the read that came up short.

Signed-off-by: LibretroAdmin <[email protected]>
2026-09-26 01:54:55 +02:00
LibretroAdmin 368b842086 replay: don't leave MOVIE_END set when playback init fails (#19622)
bsv_movie_init_playback() reads the first checkpoint and the first
frame's events before the handle is installed. Every short-read branch
in bsv_movie_load_checkpoint() and bsv_movie_read_next_events() (the
latter called with end_movie=true from bsv_movie_reset_playback()) sets
BSV_FLAG_MOVIE_END on the global input state. When init then fails, the
handle is freed and never enqueued, so BSV_FLAG_MOVIE_PLAYBACK is never
set alongside MOVIE_END.

The run loop checks MOVIE_END after every frame and issues
movie_stop() + CMD_EVENT_PAUSE. movie_stop() only clears MOVIE_END via
movie_stop_playback()/movie_stop_record(), which it never reaches
without the PLAYBACK/RECORDING flag, so the flag stays set forever:
every pause toggle runs exactly one frame and re-pauses, input looks
dead, and since Close Content goes through the same movie_stop() the
flag carries into the next core and content. Only a process restart
clears it. A truncated replay is one trigger; a replay halted before
any frame was recorded (EOF at the first frame) is another.

Fix:
- bsv_movie_init_internal(): clear MOVIE_END on the error path. A
  handle that failed to initialise leaves no flag behind.
- movie_stop(): also clear MOVIE_END when no movie is active, so a
  stray flag never survives Close Content.
- bsv_movie_start_playback(): push the "failed to load movie file"
  OSD message on failure. It runs from the task callback after
  CMD_EVENT_PLAY_REPLAY already returned success, so the command's own
  failure message never fired for an unreadable file.

Signed-off-by: LibretroAdmin <[email protected]>
2026-09-26 01:39:33 +02:00
libretroadmin b40db2251a build: ask for a reconfigure when the wayland protocol list changes
configure generates the Wayland protocol sources, so a tree configured
before a protocol was added has no way to make the new object and stops
on a missing file. config.mk depends on the generator script too, so
make asks for the reconfigure that produces it, as it already does for
configure and qb.
2026-09-25 19:53:45 +02:00
libretroadmin 0c0cd89068 wayland: tell the compositor the brightness the GL frame carries
Send Frame Luminance, off by default, describes the GL drivers' HDR
frame to a Wayland compositor with its own luminances instead of as
Windows-scRGB: the same extended-linear sRGB frame at BT.709
primaries, with the reference white at the Peak Brightness setting's
paper white and the peak at what the display or that setting gives, so
the compositor maps it from what the frame holds rather than from an
assumption about scRGB content. The parametric description is used
only where the compositor advertises the parametric and set_luminances
features with the transfer function and primaries it needs; anywhere
else, and with the setting off, the surface carries the scRGB
description exactly as before.

samples/gfx/wayland_color_management gains the parametric path against
its compositor, which rejects a creator made without the feature, a
property set twice and a description created with one unset, and the
fallback to scRGB where the features are missing.
2026-09-25 19:48:44 +02:00
libretroadmin 0e3b24938c metal: tell the display what luminances the HDR frame carries
The Metal driver sets its layer's EDR metadata, as the D3D and Vulkan
drivers set theirs: the peak is the display's where Use Display Peak
supplies one, else the user's Peak Brightness, with the optical output
scale the layer's own colour space calls for - 10,000 nits for PQ, 80
for extended-linear scRGB. A display that reads it maps the picture to
what the frame actually contains rather than to its own guess; one that
ignores it behaves as before, as does an OS below CAEDRMetadata's, and
the metadata is dropped again when HDR goes off.
2026-09-25 19:33:59 +02:00
libretroadmin ad6af1eb8d netplay: carry the session's bools in one flag word
struct netplay keeps its fourteen boolean states - server or client,
CRC checking, pausing, replay and catch-up, the forced rewind, reset
and savestate send, and the host settings - as bits of one uint16_t,
enum netplay_flags, rather than as separate bools. Conditions test the
bit; the one value sent to peers, whether pausing is allowed, is
converted to 1 or 0 before it goes on the wire.
2026-09-25 19:00:57 +02:00
libretroadmin b8d61c6834 samples: check the modes KMS lists to the modeline engine
display_servers_kms expects the connector's modes from
kms_display_server_modeline_enum(), which lists them so the engine can
score one when generation is off (#19619), rather than the empty list
of before: the mode's whole DRM timing, tagged DRM/KMS, and marked the
desktop mode where it is the live one, with an empty list and no
failure while there is no connector. Each failure exit frees the
display server it took, so a failing check reports itself rather than
the leak behind it.
2026-09-25 18:51:28 +02:00
libretroadmin 57da4697cc wayland: generate the wlroots output protocol with any wayland-scanner
The bundled wlr-output-management protocol leaves out the enum
attributes that name another interface's enumeration
(wl_output.transform, zwlr_output_head_v1.adaptive_sync_state).
wayland-scanner resolves such a reference only against the protocol it
is given, so scanners before 1.15 stop with "could not find
enumeration", which failed every build generating the bundled
protocols. The attributes annotate what an argument means and change no
generated C; the arguments keep the types the protocol gives them.
2026-09-25 18:47:14 +02:00
libretroadmin 9f3c519888 settings: name the GPU index descriptor files for the driver they hold
Four GPU index descriptor files carry the row of another driver than
the one their name gives: settings_def_gpu_index_d3d11.h holds
Direct3D 10's, _d3d12.h Direct3D 11's, _gl.h Direct3D 12's and
_vulkan.h Metal's. They are renamed for what they hold, and the four
sources that include them follow.
2026-09-25 17:35:35 +02:00
libretroadmin f6f9fb2642 video: carry the menu frame settings' bools in one flag word
video_frame_menu_settings_t keeps its 29 boolean settings as bits of
one uint32_t flags word, enum video_frame_menu_flags, rather than as
separate bools. video_driver_build_info sets the word from the same
settings as before, and the menu drivers read the bits through
'? true : false', so a flag stays true when bool is a narrow type.
2026-09-25 17:31:35 +02:00
libretroadmin bf21ab2a08 samples: time every video filter at one worker and at N
filter_plugins_test --bench, or make -C samples/gfx/filter_plugins
bench, times each video filter over 256x224 frames at one worker and
at as many workers as there are CPUs online (--threads N or THREADS=N
to choose), and prints the median per-frame time of each and the
speedup. The workers are kept alive across frames and woken once a
frame, as RetroArch's dispatcher does, so the times include what the
wake and join cost. It is not part of make check.
2026-09-25 17:31:31 +02:00
libretroadmin 2141cd1676 wayland: do not wait on the compositor after a Mutter switch
A mode switch through Mutter returns once it is asked of Mutter's
worker, which applies it later, so the Wayland display server no
longer follows it with a roundtrip on its connection. The roundtrip
waited on the compositor for the new mode's wl_output events, which
cannot come before the worker has applied the switch; they arrive
through the display server's pump when they come, as they already did.
2026-09-25 17:10:37 +02:00
libretroadmin 931fe84df6 wayland: list and switch display modes on wlroots compositors
The Wayland display server lists and switches modes on sway, Hyprland,
river and the other compositors that offer wlroots' output management,
as it does on GNOME and KDE. gfx/common/wayland_wlr_output.c speaks
zwlr_output_manager_v1 on the display server's connection: the heads
and their modes as the compositor describes them, and a switch as a
configuration carrying the last done serial and every head exactly
once, as the protocol requires - the chosen head with its new mode,
the other enabled heads as they are, the disabled ones disabled. The
compositor's succeeded, failed or cancelled answer is logged when it
comes, so nothing waits.

Mutter and KWin keep their place, and compositors with none of the
three still have no resolution list. The protocol is bundled from
wlr-protocols in deps/wayland-protocols/wlr. samples/gfx/wayland_wlr_output
checks the backend against an in-process compositor that rejects a
configuration leaving a head out or naming one twice, at manager
versions 4 and 2; the samples linking dispserv_wl.c link it too.
2026-09-25 17:10:34 +02:00
libretroadmin 886caa0007 vga: convert 32-bit menu frames too
vga_set_texture_frame downscales ARGB8888 menu frames into the BGR332
menu buffer the way it does RGBA4444 ones, taking each channel's top
bits as the core frame path does, rather than leaving the buffer
holding whatever it held before.
2026-09-25 16:49:35 +02:00
libretroadmin 8e764ec181 wayland: list and switch display modes on KWin
The Wayland display server lists and switches modes on KWin as it does
on GNOME, so Screen Resolution and the refresh rate autoswitch work
under KDE Plasma. gfx/common/wayland_kwin_output.c speaks KWin's own
output protocols on the display server's connection: the outputs and
their modes from kde_output_device_v2, published through its registry
from version 21 and as globals before it, and a switch through a
kde_output_management_v2 configuration, whose applied or failed answer
is logged when KWin gives it, so nothing waits. The modes are those of
the output the window is on, matched by connector name.

Mutter keeps its place on GNOME, and every other compositor has no
resolution list, as before. The two protocols are bundled from
plasma-wayland-protocols (MIT-CMU) in deps/wayland-protocols/kde, so
nothing of KDE's is needed to build. samples/gfx/wayland_kwin_output
checks the backend against an in-process compositor speaking both
forms; the samples linking dispserv_wl.c link it too.
2026-09-25 16:44:44 +02:00
libretroadmin 55b5c0f81d ntsc_crt: decode bands of scan lines on the workers RetroArch asks for
Demodulation runs in two passes. crt_demodulate_begin adds the noise,
finds vertical sync and, in line order, the horizontal sync and colour
carrier that carry from one line to the next, recording each active
line's decoding parameters. crt_demodulate_lines then equalises and
resamples a range of lines from those parameters alone, with its own
copies of the equalisers and its own line scratch in place of the
function-static one.

The filter's packets pass converts the input, modulates it and runs
the first pass once a frame; each worker decodes a band of lines and
copies the band of output rows those lines own. The output is the
single-worker output frame for frame, and about three quarters of a
frame's work is spread across the workers.
2026-09-25 16:43:24 +02:00
libretroadmin 96bc5a3f66 blargg_ntsc_snes: start each band at its own burst phase; run on the workers RetroArch asks for
The colour burst phase advances one step a row, so each worker's band
of rows starts at the frame's phase plus its first row, where a single
pass would be on that row. The per-frame toggle of the phase, used when
fields are not merged, happens once a frame before the workers run
instead of once per worker, which also leaves the workers nothing
shared to write. The filter runs on as many workers as RetroArch
creates it with, giving the single-worker output frame for frame.
2026-09-25 16:28:16 +02:00
libretroadmin bb7602686d phosphor2x: write the last pixel of each line; run on the workers RetroArch asks for
The horizontal stretch writes the pixel past the last source pixel as
black instead of blending it from whatever the buffer held, so the
partial clear of the output that it relied on - its length taken in
pixels as bytes, covering only part of each band - is gone. The blue
phosphor pass stops at the end of the line rather than writing one
pixel past it. The image is unchanged.

The filter runs on as many workers as RetroArch creates it with,
giving the single-worker output.
2026-09-25 16:23:21 +02:00
libretroadmin 758781e170 lq2x: take the rows above and below from the frame; average XRGB8888 per channel
Each pixel's upper and lower neighbours are the rows above and below it
in the frame, repeating the edge row only at the frame's own top and
bottom. The last worker's band had used its own row as the one below
for every row, which with one worker is the whole frame, so the filter
compared neighbours only across and above. It now runs on as many
workers as RetroArch creates it with, giving the single-worker output.

The XRGB8888 averages clear the lowest bit of every byte before
halving, so an odd sum in one channel no longer shifts into the top of
the channel below it: averaging green 1 with green 0 had given blue 128.
2026-09-25 16:23:17 +02:00
LibretroAdmin a1fc1c20cd KMS: list the connector's modes to the modeline engine (#19619)
kms_display_server_modeline_enum() returned 0, so the engine started
on KMS with no modes at all. With modeline_generation off there is
no generated candidate either, so mode selection had nothing to score
and failed with "Could not find a video mode that meets your specs".

Fill the list from g_drm_connector->modes, each with its whole DRM
timing, tagged MODELINE_TIMING_DRMKMS, with the entry equal to
g_drm_mode marked as the desktop mode - the same shape the XRandR
server already provides. Setting one goes through the existing path:
modeline_set() copies the timing into crt_switch_st and drm_ctx
rebuilds the drmModeModeInfo from it, so an existing mode round-trips
exactly.

g_drm_connector is NULL between a context teardown and the reinit;
the list is empty then rather than an error, so a generated mode
still goes through.

Not a regression from the in-tree engine: the previous KMS path
used switchres's "dummy" display, which enumerated nothing either.
With generation on the existing modes now compete in scoring the
way they already do under X11.
2026-09-25 16:14:31 +02:00
libretroadmin 313ddb3197 2xbr, 2xsai, super2xsai, supereagle, epx: run on the workers RetroArch asks for
These filters split the frame into row bands across as many workers as
RetroArch creates them with, one per core, rather than one; each band
reads its neighbouring rows from the frame, so the output is the same
as with a single worker. A single-core machine still gets one worker.

The filter plugin harness runs every video filter that takes more than
one worker with 2, 3 and 4 workers at once at an odd frame size and
checks the output byte for byte against a single worker's, and the
Linux-filter-plugins workflow runs it under ThreadSanitizer as well.
2026-09-25 16:14:02 +02:00
LibretroAdmin 176e996302 CRT: .switchres.ini geometry overrides survive the slider write (#19618)
The core/directory/game .switchres.ini files are loaded through
crt_adjust_ini(), then switch_res_crt() writes the H-Size, H-Shift
and V-Shift sliders onto the generator right before modeline_get(),
so an h_size/h_shift/v_shift the files set never reached the mode:
with the sliders at their defaults the override became 1.0/0/0.

The sliders are now the base the files refine. crt_adjust_ini()
seeds the generator from the sliders before loading the files,
records which of the three the files changed and their values, and
switch_res_crt() writes the recorded value for those until that
slider is moved, after which the slider wins for that one value.
The write still happens on every switch because the generator
clamps the geometry in place per mode.

Two things this uncovered:

- current_content_name was never filled, so the content-change
  check fired on every mode change and re-parsed switchres.ini and
  all the override files each time. It is now filled from the
  content basename the way crt_adjust_ini() records it.

- On the first switch the slider values carried in from the config
  counted as a move; crt_adjust_ini() now takes the slider state as
  seen, so a config value does not clobber a file's override on the
  first mode.

samples/gfx/crt_switch_modeline gains a case with a game override
file: its h_shift/v_shift reach the generator with the sliders at
default, survive the next mode change, are parsed once per content
rather than per switch, a moved slider takes only its own value
back, and other content without a file returns to the sliders.
2026-09-25 16:09:52 +02:00
libretroadmin 9390e5d274 2xbr: compare precomputed YUV keys in the edge decisions
Once a frame, before the workers run, the filter fills a buffer with
one YUV key per source pixel, RGBtoYUV of its RGB565 value. The edge
decisions load the keys of the 5x5 neighbours with the same clamped
indices as the pixels and compare them by neighbour name, so each
comparison is a subtraction instead of two conversions and table
lookups, and the outer ring of the neighbourhood, used only in
comparisons, is no longer loaded as pixels. The output is unchanged;
at 256x224 the XRGB8888 path takes about 60% of its previous time.
2026-09-25 16:08:43 +02:00
libretroadmin b0f0d9ba26 2xbr: make the XRGB8888 edge decisions through the YUV table; build filters optimised
The XRGB8888 path makes its edge decisions at RGB565 precision through
the same YUV table as the RGB565 path, two lookups per comparison in
place of a double-precision YUV transform, and blends at full 8-bit
precision as before. At 256x224 it runs in about the RGB565 path's
time, around a fifth of what the floating-point comparisons took.
df8 and eq8 are macros local to the file rather than exported symbols.

The video filter Makefile defaults to a release build, as the DSP
filter Makefile does, so the plugins are built with -O2 unless
build=debug is given.
2026-09-25 16:05:28 +02:00
libretroadmin fa9eb93e04 filters: link the plugins against libm; load and run them in CI
The video filter and audio DSP filter Makefiles name libm after the
objects, so --as-needed keeps it and the plugins that use math
functions record it; they loaded only into a host that had libm
already.

samples/gfx/filter_plugins loads every plugin with RTLD_NOW into a
host that does not link libm and runs a frame or a run of audio blocks
through it at its default settings. The Linux-filter-plugins workflow
runs it as built and again under ASan, UBSan and LeakSanitizer.
2026-09-25 15:54:02 +02:00
libretroadmin 246d4372c9 2xbr: filter over the whole 5x5 neighbourhood
Each pixel reads the two rows above and below it in the frame, and at
the frame's edges the rows and columns repeat the edge rather than
reading outside it. With the filter on its single worker the row step
had been zero for every row, so the neighbours above and below were
the pixel's own row, none of 2xBR's edge rules could fire and the
output was plain pixel doubling, identical to normal2x. It is now
2xBR's.
2026-09-25 15:53:59 +02:00
libretroadmin 0565336164 ntsc_crt: clear the CRT output buffer, keep the modulator's arithmetic defined
The CRT output buffer is cleared when it is allocated and whenever it
is resized. crt_demodulate does not write every pixel of it, and what
it leaves is copied to the screen, so without the clear those pixels
showed whatever the heap held and changed from run to run.

The noise generator wraps in unsigned arithmetic, and the negative
filter coefficients and luma samples are scaled by multiplication
rather than left-shifted; the output is unchanged.
2026-09-25 15:53:55 +02:00
libretroadmin c98957fdcb ntsc, ntsc_crt: widen RGB565 input with its top bits copied down
The NTSC filters widen RGB565 channels to 8 bits by replicating each
channel's top bits into the new low bits, as the rest of RetroArch
does, so white enters the filter at full scale and greys stay
neutral, rather than as 248/252/248 with green a step ahead of red
and blue.
2026-09-25 14:46:58 +02:00
libretroadmin dba7c1e66a fpga, vga: step source rows by the frame's pitch
The scale loops find each source row pitch bytes into the frame
rather than width pixels, so a core frame whose rows are padded is
read at its own stride instead of coming out skewed. Tightly packed
frames, and the menu frames these drivers pack themselves, read
exactly as before.
2026-09-25 14:21:18 +02:00
libretroadmin cc0021051f ci: compile the DOS VGA driver in the compile matrix
vga_gfx.c is built against stub dpmi.h and pc.h carrying the DPMI
register block, the real-mode interrupt call, dosmemput and the port
I/O it uses, in the shapes DJGPP gives them, with declarations after
statements rejected. Outside the DJGPP job nothing compiled it.
2026-09-25 14:00:29 +02:00
libretroadmin 581c2796ac pixconv: SSE2 for the last scalar converters, NEON for ARGB8888 to 4444
conv_argb8888_rgba4444, conv_argb8888_0rgb1555 and conv_rgba4444_rgb565
have SSE2 paths, and conv_argb8888_rgba4444 a NEON one. The 32-bit
sources narrow with _mm_packs_epi32, the 4444 results sign-extended
first so values past 0x7fff are not saturated. conv_bgr24_argb8888
reads four pixels as three 32-bit words on little-endian hosts that
have no NEON path. conv_bgr24_rgb565 keeps its byte loop, which the
word loads did not beat.
2026-09-25 13:50:01 +02:00
libretroadmin 7ebb02cdcc gl1: declare the Vita depth-test flag at the top of its block
The compile matrix builds gl1.c's Vita branches against a vitaGL stub
over the host GL headers, rejecting declarations after statements the
way the PSP lane does; nothing else compiles them outside the Vita
toolchain.
2026-09-25 13:41:27 +02:00
libretroadmin 4e12008793 screenshot: free the task state after a synchronous dump
screenshot_dump() without a thread writes the file and then releases
its state and, once written, the caller's buffer - the same ownership
the task path has, where a failed write leaves the buffer the caller's
to free. It also releases the state when the screenshot directory
cannot be created. --max-frames-ss and save-state thumbnails both take
the synchronous path, and each call left the state and a full captured
frame behind.

The ASan/UBSan workflow takes a screenshot on exit under
LeakSanitizer, with only the system libraries suppressed, and fails on
any leak left over.
2026-09-25 13:41:23 +02:00
libretroadmin d06b53d690 gl1: upload core and menu frames without a staging copy
gl1_draw_tex updates its texture in place with glTexSubImage2D, reading
the frame at its own pitch through GL_UNPACK_ROW_LENGTH, and respecifies
the storage, cleared, only when the power-of-two size or internal
format changes. XRGB8888 frames go up straight from the core's buffer,
RGB565 frames as GL_UNSIGNED_SHORT_5_6_5 on GL 1.2 and newer, and
RGUI's RGBA4444 framebuffer as it is; the no-GL_EXT_bgra swizzle writes
just the frame, tightly packed. GL 1.1 without packed pixels, the HDR10
tonemap and the Vita build, which has no GL_UNPACK_ROW_LENGTH, keep
their staging buffers.

samples/gfx/gl1_upload runs the driver under Xvfb with a test core and
checks the screen pixel for pixel down every upload path, threaded
video and frame size changes included, in the Linux-gl1-upload
workflow.
2026-09-25 04:33:38 +02:00
libretroadmin 9a9dc0ffde pixconv: NEON for the remaining converters; fix SSE2 565 to 1555
conv_rgb565_0rgb1555, conv_0rgb1555_rgb565, conv_rgba4444_rgb565,
conv_bgr24_argb8888, conv_bgr24_rgb565, conv_argb8888_0rgb1555,
conv_argb8888_bgr24, conv_abgr8888_bgr24 and conv_yuyv_argb8888 have
NEON paths, so every converter the scaler dispatches to is vectorised
on ARM. YUYV keeps the scalar path's 16-bit fixed point, with
vqmovun_s16 doing the clamp. The SSE2 path of conv_rgb565_0rgb1555
shifts red and green down one bit, matching its MMX and scalar paths.

The pixconv test covers every converter, including the 24-bit and
YUYV sources, so each SIMD path is checked against a reference on
x86 and, in the cross-arch lane, on aarch64, armv7 and PowerPC.
2026-09-25 04:13:43 +02:00
libretroadmin 1375d13f74 samples: stub the HDR metadata peak for vulkan_validation
vulkan_common.c asks the video driver for the peak its HDR metadata
carries; the sample's stub answers with the driver's own value, as the
frontend does with no display peak in use.
2026-09-25 04:01:17 +02:00
libretroadmin 6d536cff0f video: Use Display Peak, and the display's peak on Windows and Android
Use Display Peak, off by default, makes the peak luminance the display
reports stand in for Peak Brightness wherever a peak is used: what
cores are told through GET_HDR_MAX_NITS, and the HDR metadata the
drivers send - DXGI's for D3D11 and D3D12, Vulkan's vkSetHdrMetadataEXT
(which reaches the compositor under Wayland), and the KMS blob. Off, or
with no reported peak, every value stays as it was: the setting for
cores, each driver's fixed 1000 nits for its metadata.

Windows reports the panel's peak through DXGI_OUTPUT_DESC1, read where
the HDR check already reads it and passed on only when it changes, and
Android through HdrCapabilities (API 24), queried once by its display
server; Wayland and KMS already did. Apple's EDR APIs give a headroom
over the current SDR white, never an absolute peak, so macOS and iOS
report none.
2026-09-25 03:21:58 +02:00
libretroadmin 9612092490 video: show the display's own peak luminance beside the HDR setting
The Wayland and KMS contexts learn the display's peak luminance where
it is on offer - the compositor's description of the output
(wp_color_management_output_v1, its target luminance) and the sink's
EDID - log it, and hand it to the video driver, whose Peak Brightness
entry shows it beside the setting, as "1000 (display: 650)". The
setting's own value is unchanged and nothing applies the display's:
peak luminance is something users calibrate around.

The compositor's description is read without waiting, from events the
context already dispatches: its target luminance, not the transfer
function's range - 10,000 nits for PQ - which says nothing about the
panel. samples/gfx/wayland_color_management covers the query against
its compositor, with and without a target luminance.
2026-09-25 02:54:09 +02:00
libretroadmin 393c9b8b24 pixconv: NEON paths for the 0RGB1555, RGBA4444 and BGR24 converters
conv_0rgb1555_argb8888, conv_rgba4444_argb8888, conv_0rgb1555_bgr24
and conv_rgb565_bgr24 convert eight pixels per step on ARM NEON. Each
channel is shifted to the top of its lane with its own top bits
inserted beneath, so narrowing gives the bit-replicated 8-bit value;
the BGR24 variants store through vst3_u8. Output matches the scalar
path bit for bit.

The cross-arch samples lane runs the pixconv test under qemu on
aarch64, armv7 with and without NEON, and big-endian PowerPC, and the
scalar test build leaves out NEON as well as SSE2 and MMX.
2026-09-25 02:52:59 +02:00
github-actions 55286e6c00 Fetch translations from Crowdin 2026-09-25 00:32:03 +00:00
libretroadmin 82c33d2c3e kms: HDR10 output for the GL drivers
The KMS context drives an HDR10 sink when HDR is on: where the sink's
EDID declares SMPTE ST.2084 and the connector has HDR_OUTPUT_METADATA
and a Colorspace with BT2020_RGB, it sets static metadata built from the
user's peak (capped at the sink's), Rec.2020 and 10 bits, then scans out
XRGB2101010 from a 10-bit EGL config. The properties are set before the
surface exists; if the connector refuses them, or there is no 10-bit
surface or config, what changed is put back and the output stays SDR,
so a PQ picture never reaches a sink not told to expect it. The
connector is restored on the way out. GFX_CTX_FLAGS_HDR10_FRAMEBUFFER
tells the video driver its framebuffer is Rec.2020 PQ.

gl and glcore render it through their scRGB offscreen and encode pass,
finished in PQ: the encode shaders gain an HDR10 output switch - a
uniform in gl2's, a UBO field in glcore's, off everywhere else - that
rotates the composited scRGB to Rec.2020 and PQ-encodes it at absolute
luminance, a PQ source passing through as it came. The native HDR
screenshot read-back, which reads an FP16 backbuffer, stands aside.
gfx/common/drm_hdr.c holds the EDID parse and metadata build as pure
functions, which samples/gfx/drm_hdr checks, including the blob layout
against the kernel's own struct.
2026-09-25 02:17:16 +02:00
libretroadmin a1ae21a756 video: the GL GPU index on X11 EGL and KMS, and only where it can choose
The X11 EGL context chooses its GPU as the Wayland one does: it lists
EGL's devices, makes its display on the chosen one through
EGL_EXT_explicit_device, falls back to the default display where the
device cannot drive it, and publishes the list to the menu. The KMS
context's display follows the card it opens, through GBM, so there the
chosen device's DRM card (EGL_EXT_device_drm, now kept per device by
egl_common) is tried before the others, which follow in their usual
order when it has no usable output.

The GL drivers' GPU Index entry appears only while the context has
published such a list; GLX, and any context without device enumeration,
has nothing to choose from and shows none.
2026-09-25 01:51:15 +02:00
libretroadmin 0f1b7c8ca9 wayland: present the FP16 scRGB surface opaque
The GL drivers' scRGB frame carries alpha - their frame target clears to
transparent black and the encode pass keeps its alpha - and on Wayland
an RGBA16F surface's alpha is blended by the compositor, the opaque
region being only a hint, so the letterbox around a core's frame showed
whatever lay behind the window. The context now creates that surface
with EGL_EXT_present_opaque, which egl_common applies to the next window
surface when the context asks, and scRGB is offered only where the
display has it.
2026-09-25 01:42:53 +02:00
libretroadmin e795894c9b ci: build the configuration with each GPU index owner on its own
compile-matrix.sh builds configuration.c with EGL on and Vulkan off, and
with Vulkan on and EGL off. Each video API owns a GPU index setting and
its default under its own gate, and a build has any mix of APIs; a
default defined under another API's gate builds only where both are on,
as the GL GPU index default did before it moved under HAVE_EGL, which
broke every EGL build without Vulkan, the PS Vita's among them.
2026-09-25 01:19:10 +02:00
libretroadmin 6dc05aa557 Remove unused variable 2026-09-25 01:19:03 +02:00
libretroadmin 9af8aea1a5 config: define the GL GPU index default in every EGL build
DEFAULT_GL_GPU_INDEX is defined wherever the gl_gpu_index setting is,
under HAVE_EGL, rather than inside the Vulkan block, so EGL builds
without Vulkan build again.
2026-09-25 01:11:12 +02:00
sonninnos f494e600a6 Use common value label for video_shader_watch_files since it is a normal bool 2026-09-25 02:13:19 +03:00
libretroadmin 9b2a7f8bd1 video: keep the GL drivers' GPU list for the menu
The GPU list the Wayland context publishes for the GL drivers has a slot
of its own, for OpenGL and OpenGL ES alike, so the GPU Index entry names
each device and steps through them as it does for Vulkan and Direct3D.
A device's label leaves out the vendor where its renderer string
already begins with it, as NVIDIA's does.
2026-09-25 01:09:34 +02:00
libretroadmin a618658334 pixconv: packed 16-bit pixel expanders; fix rgba4444 and B5G5R5A1 alpha
pixconv.h provides single-pixel expanders from RGB565, 0RGB1555,
RGBA4444 and ARGB4444 to 32-bit that move every channel into its byte
lane and widen them together with one shift and mask (a multiply by
0x11 for 4-bit channels). The pixconv scalar paths, dxgi_copy, the PNG
and BMP encoders and the HUB75 driver use them. conv_argb8888_rgba4444
takes the high nibble of each channel, dxgi_copy reads and writes
B5G5R5A1 alpha at bit 15 and widens it to full scale, and the MMX
paths load and store through memcpy since rows need not be 8-byte
aligned.

libretro-common/samples/gfx/pixconv runs every 16-bit input through
each converter with SSE2, with MMX only and with no SIMD, and is part
of the libretro-common samples workflow.
2026-09-25 01:00:50 +02:00
libretroadmin 06781a4041 menu: offer the GPU Index entry for the GL drivers
The GPU Index entry is a row per video driver, bound to that driver's
own index and added only while it runs; gl, glcore and gl1 get theirs,
bound to gl_gpu_index, so the GL GPU list the Wayland context publishes
reaches the menu. The entry's label reads the bound index again, as it
does for every other driver.
2026-09-25 01:00:46 +02:00
libretroadmin fa408a551f wayland: choose the GPU the GL drivers render on
The GL drivers on Wayland take a GPU index as Vulkan does. The context
lists EGL's hardware devices through EGL_EXT_device_enumeration - under
glvnd every vendor's, so an NVIDIA GPU beside a Mesa one - labels them
by vendor, renderer and DRM node, and makes its display on the chosen
one through EGL_EXT_explicit_device. Index 0 leaves the choice to the
EGL implementation, as before; a device that cannot drive the display
gives way to the default with a warning. The list reaches the GPU Index
entry like Vulkan's, whose label now shows the running API's own index.

egl_common gains the device list and a device hint for the next display
it makes, usable by any EGL context.
2026-09-25 00:38:18 +02:00
libretroadmin b24cb4ee5c wayland: HDR for the GL drivers through the compositor's colour management
The GL drivers get HDR on Wayland the way they have it on Windows: an
FP16 framebuffer composited as scRGB, which gl, glcore and gl1 already
render to when the context reports GFX_CTX_FLAGS_SCRGB_FRAMEBUFFER. No
EGL driver tags a Wayland surface's colour space for GL, so the context
does it through wp_color_manager_v1: where the compositor offers the
windows_scrgb feature with a perceptual intent, EGL has an RGBA16F
config (EGL_EXT_pixel_format_float) and HDR is on, the context takes
that config, marks the surface opaque and tags it Windows-scRGB. The
tag is set from the image description's ready event, as the protocol
requires, during the context's own dispatch, so nothing waits for it.
The HDR settings are offered where the compositor and EGL could do it;
GL HDR is scRGB only, so HDR10 maps to it. The Vulkan context binds the
manager but never tags its surface, which the Vulkan driver's WSI owns.

gfx/common/wayland_color.{c,h} holds the protocol client, built on the
colour-management protocol now bundled in deps/wayland-protocols.
samples/gfx/wayland_color_management runs it against an in-process
compositor that records every fatal rule a client could break -
Windows-scRGB without the feature, a description set before it is
ready, an intent not offered, a second colour-management object on a
surface - and CI runs it under ASan and UBSan.
2026-09-25 00:05:05 +02:00
libretroadmin d048deca5c wayland: report the DRM lease once, where the log is on to show it
The Wayland display server's DRM lease report is only a log line, so
it is started once per instance through wl_display_server_report_lease()
and only where the log is on: video_display_server_init() starts it when
it makes or keeps dispserv_wl with verbosity enabled. The early display
server init runs before the command line turns the log on, and its
report used to race that, appearing on some starts and not others;
now the report comes with the video driver's context, after
"Found display server", on every start, and without the log no report
connection is made at all.
2026-09-24 22:41:17 +02:00
libretroadmin f24b1a7217 video: keep the early Wayland display server for the driver's session
The display server set up early from WAYLAND_DISPLAY, before the video
driver has a window, is kept when the driver's context declares Wayland
too, instead of being destroyed and made again. dispserv_wl keeps a
connection of its own whatever window the driver makes, so the early
one serves the session as it is, and startup makes one connection and
reports the DRM lease once.
2026-09-24 22:25:48 +02:00
libretroadmin 914e2e7cea frontend: give the audio thread a short EEVDF slice, without privilege
Since Linux 6.12 a time-shared thread may ask for its own EEVDF slice
through sched_setattr(), with no capability or rlimit. A new backend,
frontend/thread_elevation/eevdf.c, asks for the kernel's shortest, 0.1
ms, so the thread gets an earlier deadline and the CPU sooner when it
wakes, with its share of the CPU unchanged. It leaves a real-time,
batch or idle thread alone, and counts only when the slice reads back as
asked, which kernels before 6.12 do not do. Under two busy threads on
one CPU, an audio-shaped thread's p99 wake-up lateness drops from about
3 ms to under 0.1 ms, and a burst longer than the slice completes no
later than before.

The chain gains additive backends: self-acting, tried first, and never
ending the chain, since a scheduler hint complements a raised priority
rather than standing in for it; a continuation never reaches one.
thread_elevation_raise_current() reports one that applied, and the
audio thread logs it. samples/frontend/thread_elevation covers additive
ordering and continuation, and runs the real backend on the kernel it
finds: granted with the slice read back where it is supported, and a
real-time thread left as it is.
2026-09-24 22:08:41 +02:00
libretroadmin 3b3453e38b wayland: native contexts use the Wayland display server, which never waits
The Wayland contexts declare RARCH_DISPLAY_WAYLAND once their display
is up, as the X11 ones do, so a native Wayland session gets dispserv_wl
- the output's refresh rate, size, metrics and EDID, and on GNOME
Mutter's resolution list and refresh-rate switching - rather than the
null display server. dispserv_wl keeps a connection of its own, used
only on the thread that calls it, and makes no roundtrip: init asks for
the registry and returns, and every call first takes whatever the
compositor has sent without waiting for more, so the output is bound
and described over the first few calls. The DRM lease report, which
needs answers, runs on a detached thread with a connection of its own.

compile-matrix.sh builds the D-Bus, Mutter and RealtimeKit units and
the elevation chain as a target with no POSIX headers, against
tools/platform_stubs/no_posix, where any of them reaching <unistd.h>,
<poll.h>, <fcntl.h>, <dlfcn.h> or <sys/*.h> outside its platform gate
fails. samples/gfx/wayland_drm_lease builds with threads, as RetroArch
does, and waits for the report; samples/gfx/mutter_displayconfig checks
that the output is described without a roundtrip.
2026-09-24 21:32:01 +02:00
libretroadmin 1d52796dbb Silence qualifier and unused-variable warnings on MSVC, glibc and X11
Where retro_atomic_int_t is volatile (the MSVC backend), the address
of a waiter's flags or of the audio reinit request travels through
volatile-qualified pointers: the runtime-resolved wait-on-address
entry points and the MONITORX/UMONITOR helpers take a volatile
address, and wasapi hands the reinit request to the watcher thread
through an explicit void pointer, which mmdevice_thread casts back.

The autodetect index parser keeps the strchr() result in a const
pointer, so the qualifier-preserving strchr of C23 glibc has nothing
to discard, and the X11 modeline path drops a crtc counter it never
reads.
2026-09-24 21:30:22 +02:00
libretroadmin 1865a93c24 griffin: build the D-Bus/Mutter/RealtimeKit units only on Linux and BSD
Griffin mirrors Makefile.common, which adds dbus_runtime, dbus_common,
mutter_displayconfig and rtkit only for Linux and BSD targets, so
griffin.c includes them under the same platform set instead of under
HAVE_THREADS alone. Windows, consoles and other griffin targets no
longer see these units at all.

mutter_displayconfig.c keeps <unistd.h> inside RARCH_HAVE_MUTTER_DC,
next to the pipe/poll code that needs it, so the file stays an empty
translation unit on any platform where the Mutter backend is off.
2026-09-24 21:16:11 +02:00
libretroadmin b8f36709aa mutter: follow GNOME's display modes on a worker, over libdbus loaded at run time
Mutter's org.gnome.Mutter.DisplayConfig is used by every threaded Linux
and BSD build, through libdbus loaded at run time, and nothing that
calls into it waits on the bus. One worker for the process connects,
subscribes to MonitorsChanged and publishes Mutter's state, reading it
again after every change; it waits in poll() on the bus socket and a
wake pipe. The resolution list and availability are answered from the
published state under a lock the worker takes only to swap it, so the
menu, the refresh-rate check and the runloop's autoswitch no longer
make a D-Bus round trip. A switch is checked against that state at once
- a size or rate the head does not list is refused without asking
Mutter - and applied by the worker against the state Mutter reports
then, with the serial, layout and carried-over settings as before.
Until the worker has answered, or where Mutter is not there, callers
keep their own behaviour.

dispserv_wl and dispserv_x11 ask for Mutter at each call rather than
once at init. The runtime libdbus table gains the container, append,
match and dispatch entry points this needs, checked against libdbus's
headers by samples/frontend/dbus_runtime. samples/gfx/mutter_displayconfig
builds on the loader and waits for the worker where it used to get a
synchronous answer, and its mock announces MonitorsChanged as Mutter
does.
2026-09-24 21:12:46 +02:00
libretroadmin e1a497fbdd dbus: suspend the screensaver on a worker, through libdbus loaded at run time
Screensaver inhibit over org.freedesktop.ScreenSaver works in every
threaded Linux and BSD build, on libdbus loaded at run time. One worker
for the process loads the library, opens and closes the session-bus
connection and makes the Inhibit and UnInhibit calls; the main thread
only publishes the state it wants under a lock the worker never holds
across a call, and never waits for an answer. A generation that moves
on every open keeps a slow answer from being reported for a newer
request, and losing the session bus ends the connection rather than
RetroArch.

On X11, XScreenSaver is asked alongside D-Bus on the main thread, where
its display lives. xdg-screensaver, whose suspend lasts as long as the
window, starts only once D-Bus has failed: at once when that is already
known, otherwise from x11_check_window() when the worker's answer
arrives. Wayland compositors without idle-inhibit use the same worker.
2026-09-24 20:35:21 +02:00
libretroadmin 340e129377 dbus: load libdbus at run time for RealtimeKit on every Linux build
RealtimeKit is a backend of every threaded Linux build: libdbus is
loaded when first needed through gfx/common/dbus_runtime, which mirrors
libdbus's public, ABI-stable structs and constants under names of its
own, so no D-Bus headers or link dependency are needed and a system
without the library runs as before. The rtkit helper loads it on its
own thread, so neither the audio thread nor the main thread waits on
the dynamic loader or the bus, and whichever thread loads first
publishes the table without anyone waiting. HAVE_DBUS no longer gates
the backend.

The helper now tells apart what can happen: libdbus not available, no
system bus, rtkit not running, rtkit refusing with its D-Bus error
name, and each grant. samples/frontend/dbus_runtime checks the mirror's
sizes, alignments, field offsets and constants against libdbus's own
headers, and the no-pkg-config CI job runs it.
2026-09-24 20:05:17 +02:00
libretroadmin 7193a19c47 qb: find D-Bus, Wayland, libdecor, OpenSSL and Qt without pkg-config
Without pkg-config, configure finds the packages it used to need
pkg-config for through check_nopkg(): the include directories are
looked for under the include and library directories, where D-Bus keeps
its arch header, and a probe program must build and link before the
package counts, with the CFLAGS and LIBS pkg-config would have given.
Qt is described by its own qmake -query, headers, libraries and mkspec
alike, and wayland-scanner answers for its version, with the protocol
generator using deps/wayland-protocols. With pkg-config present none of
this runs and configure is unchanged.

A package asked for by name stops configure when nothing can find it:
check_pkgconf() dies on a forced package without pkg-config unless a
library check or a check_nopkg() probe follows, and a forced Wayland
stops configure when its libraries or scanner are missing rather than
being disabled with a notice. The no-pkg-config CI job installs these
packages, configures with --enable-dbus and checks each is found and
built, then checks that a forced rsound fails and a forced pulse, which
has a library behind it, still configures.
2026-09-24 19:08:09 +02:00
libretroadmin ea89f665f2 settings: route every packed-row read and write through the half accessors
The custom viewport origin and size, the window size pairs and the
desktop menu window geometry each keep both axes in one settings_t
word, and each menu row owns one half of it. The generic uint/int
display strings, the custom viewport width/height display strings,
the dropdown builders and commits, and the Qt spin box, combo box and
radio button widgets all dereferenced value.target directly, so these
rows showed and edited the whole word: a 960x720 viewport displayed
its width as 62915280, stepped in units of 65536, clamped to 9999 in
Qt, and a Qt edit or dropdown choice overwrote the partner axis.

setting_uint_get/set and setting_int_get/set now live in
setting_list.h so the menu and the desktop UIs share them, and all of
those paths go through them. samples/settings/packed_rows drives the
rows through their display strings, a right press and the dropdown,
and runs in the menu harness workflow.
2026-09-24 19:05:35 +02:00
libretroadmin 115b1eb010 frontend: raise threads through an ordered list of elevation backends
thread_elevation_raise_current() tries each backend in
frontend/thread_elevation.c's list until one grants, and returns at
once. A self-acting backend changes the calling thread and answers
there; a brokered one acts on a thread id from any thread and may
answer PENDING, finishing on a thread of its own and carrying on with
thread_elevation_continue() if it is refused. Every self-acting backend
precedes every brokered one, so a continuation only ever meets brokered
backends. The list is rthreads' kernel request, then RealtimeKit on
D-Bus builds; the audio thread asks once and names none of them.

RealtimeKit moves out of gfx/common/dbus_common.c into
frontend/thread_elevation/rtkit.c as a brokered backend, unchanged in
what it asks for and logs. samples/frontend/thread_elevation drives the
chain runner with a scripted list: order, the thread id and resume
point a brokered backend is given, and a pending request refused on its
own thread continuing past a self-acting backend placed after it.
2026-09-24 17:01:11 +02:00
sonninnos 98a05c5099 Scanline Sync sublabel requirement addition 2026-09-24 17:35:35 +03:00
sonninnos ba2e008501 Reorder notification visibility list + add refresh to stats bool 2026-09-24 17:22:17 +03:00
sonninnos fb514f5cf7 Remove newline from runloop pace string due to Diplay Information page 2026-09-24 16:56:53 +03:00
sonninnos 28bdb34b0c Video stats alignments 2026-09-24 16:44:41 +03:00
libretroadmin ee372be96e alsa: start the device clock fits again after a pause or an xrun
Both device clock fits, from the audio timestamp and from the position,
start again from the writer's next sample whenever the device stops and
restarts: alsa_start(), alsa_stop() and every playback xrun recovery
raise an atomic flag that alsa_clock_sample() consumes. Through a pause
or an underrun the position stands still while time runs on, so a fit
spanning one reads the device as slow; the last published estimate
stands until the new window publishes its own.

alsa_clock_test covers a 2 s pause and a 300 ms underrun mid-window.
2026-09-24 15:07:16 +02:00
libretroadmin 067c652ff7 dbus: ask RealtimeKit for the audio thread when the kernel refuses
When sthread_raise_current_priority() is refused on a Linux build with
D-Bus, the audio thread hands its tid to dbus_rtkit_raise_current_thread(),
which runs the whole RealtimeKit exchange on a detached thread of its own
and returns at once; nothing waits on the bus and the outcome is only
logged. The helper asks for real-time round-robin at rtkit's
MaxRealtimePriority, capped at the middle of the band, after bringing
RLIMIT_RTTIME within rtkit's RTTimeUSecMax as rtkit requires, and falls
back to a nice value no lower than -11 or rtkit's MinNiceLevel. One
request is in flight at a time.

audio_thread_wrapper.c includes config.h so that HAVE_DBUS reaches it.
2026-09-24 15:07:12 +02:00
libretroadmin 01cca3a938 x11: disable xdg-screensaver where its D-Bus helper cannot run
On KDE 4 and later and on GNOME 3, xdg-screensaver suspend hands the
inhibit to a detached Perl helper that needs Net::DBus and
X11::Protocol. The probe reads the desktop from the environment the way
xdg-screensaver does and, on those desktops, checks that both modules
load; without them screensaver suspension is reported as disabled
instead of launching a helper that fails on stderr while the command
itself exits 0.
2026-09-24 14:19:57 +02:00
libretroadmin 1b8c56a0e7 rthreads: raise the audio thread within the process's rlimits on Linux
sthread_raise_current_priority() asks for SCHED_RR at the middle of the
band first. When that is refused on Linux it retries at RLIMIT_RTPRIO
if the limit grants a lower real-time priority, and failing that lowers
the calling thread's nice value to -11, or as far as RLIMIT_NICE lets
it. A process that holds CAP_SYS_NICE, or whose limits allow the full
request, gets exactly what it got before.

audio_thread_loop() checks its argument before touching it.
2026-09-24 14:19:54 +02:00
sonninnos cc5fecc8e7 Remote RetroPad updates (#19617)
- Change screen switch combo to LShift+Tab
- Default pointer mode to pointer
- Restart UDP socket on IP option change
- Call GET_SENSOR_INTERFACE only on page switch and not on every frame in sensor page
- Hide pointer and sensor debug under define instead of comment
- Cleanups and nits
2026-09-24 10:24:10 +02:00
github-actions 38eaee3943 Fetch translations from Crowdin 2026-09-24 00:24:27 +00:00
LibretroAdmin 8105343981 record: the output and framebuffer sizes are words
struct record_params carries out_dims and fb_dims as VIDEO_SCALE_PACK
words; ffmpeg scales and evens the output size in locals and stores
the result as a word, and sizes its buffers with VIDEO_SCALE_AREA.
2026-09-23 22:46:04 +02:00
LibretroAdmin 824a200baf video: the custom viewport is read as the settings' words
The viewport parameter snapshot keeps the custom viewport's origin and
size as the VIDEO_POS_PACK and VIDEO_SCALE_PACK words the settings
store, and vulkan's font pass sets its viewport from the video size
word it already holds.
2026-09-23 22:46:00 +02:00
LibretroAdmin bb026dd95f d3d10/11/12: a texture's mip count halves one word
The OR of the two axes is above 1 exactly when either axis is, and
halves the same, so the mip levels are counted on that one word.
2026-09-23 22:45:57 +02:00
LibretroAdmin ee03686655 gl: the scRGB offscreen, streamed and filter-chain texture sizes are words
The scRGB offscreen in gl1, gl and glcore, glcore's streamed textures
and gl3_filter_chain_texture keep their sizes as VIDEO_SCALE_PACK
words, so frame, history, feedback and framebuffer sizes pass through
the filter chain as the words they are stored in. The offscreen target
helpers, gl3_update_cpu_texture(), gl3_framebuffer_set_size(),
gl3_pass_get_output_size() and the size semantic builders take words.
2026-09-23 22:45:53 +02:00
LibretroAdmin 4a99e49828 wdmks: the loop counts its underruns for the statistics
Every driver with a pump or a callback counts the periods it filled
with silence for want of audio and hands the number to the statistics
overlay; wdmks handed nothing, so it was the one driver where the
screen could not say whether it was keeping up.

The loop knows exactly when it runs out: the hardware running past the
write cursor, or laps going by unobserved, which is where the write
cursor is resynced. That is counted - once per gap in the audio. A
start is not a gap, and neither is every further sample of a pause
finding the loop empty again: the count moves only when audio had been
placed since the last resync, so a pause of any length is one
underrun, the one where the audio stopped.

The packet path still reports nothing: a packet the device found
nothing behind is the port driver's to fill, and it does not say so.

The note on the position register that ended with "a thread of this
driver's own - which is not here yet" is brought up to date: the
refill thread has been there since it landed.

samples/audio/wdmks_loop asserts the count through the contract: nil
after a start, one when the audio runs out, still one as the pause
goes on, two after laps are lost with audio queued, and unchanged by
a restart.
2026-09-23 22:19:16 +02:00
LibretroAdmin fbf3be4e7d metal: the sprite vertex colour is four 16-bit channels
SpriteVertex carries its colour as UShort4Normalized, packed by
rgba16_pack(): a vertex is 24 bytes against 32, and the menu bake,
the font quads and the overlay store one word per vertex. The vertex
functions still read a float4, so the shaders and the prebuilt
metallib are unchanged.
2026-09-23 22:16:23 +02:00
LibretroAdmin d38c398ffe glcore: the overlay colour is four 16-bit channels
The overlay's colour stream is UNORM16, bound as normalised
GL_UNSIGNED_SHORT: eight bytes a vertex against sixteen for four
floats, and set_alpha writes the one alpha channel it changes.
2026-09-23 22:16:23 +02:00
LibretroAdmin 6f62ca8b27 vulkan: the vertex colour is four 16-bit channels
struct vk_vertex carries its colour as R16G16B16A16_UNORM, packed by
rgba16_pack() in gfx/common/rgba16_pack.h, which replaces rgba8_pack.h.
Sixteen bits keep the high-precision menu text colour and every float
fade finer than any swapchain the menus draw to, so a fade on a 10-bit
or scRGB target is not held to 256 steps; a vertex is 24 bytes against
32 for four floats.

samples/gfx/rgba16_pack holds the SSE2, NEON and scalar paths to the
same reference, including a lane that reads every 10-bit level back at
10 bits, which an 8-bit vertex colour fails.
2026-09-23 22:16:23 +02:00
sonninnos 9783568f8e Smart Integer Scale adjustments for PAL content 2026-09-23 22:57:40 +03:00
LibretroAdmin dcba79ee2f wdmks: the resync margin is the FIFO the pin reports
After the hardware runs past the write cursor - and at every start -
the write cursor is put a margin ahead of the register, so the bytes
are in place before the DMA fetches them. That margin was a fixed two
milliseconds, a guess, and on the loop it is also the floor the
reported latency can reach: a queue cannot be driven below it because
every resync puts it back.

The pin says how far past the register the DMA has already fetched:
the FIFO in its hardware latency report, which the driver was already
reading for the statistics. The margin is now that FIFO plus half a
millisecond for the write itself, and two milliseconds only where the
pin reports no FIFO. On a codec reporting a 1 ms FIFO that takes the
floor from 2.7 ms to about 1.5 ms; on a pin whose FIFO is longer than
two milliseconds the old margin was a write into bytes already gone,
a click on every resume, and the margin is now sized to it.

The latency report is taken before the pin is started, because the
first sample after a start resyncs by the margin.

samples/audio/wdmks_loop runs the contract with no FIFO report, a
1 ms one and a 10 ms one, and pins the margin's arithmetic for each.
2026-09-23 21:49:27 +02:00
LibretroAdmin 01f0c3edc9 video: a driver's frame() takes the frame size as one word
video_driver_frame packs the core's width and height once and hands
that word to the driver, the threaded wrapper passes its slot's word
through, and the drivers that compared or stored the size packed
compare and store it directly. sdl2's texture record and gl2's pass
sizing take the word too.
2026-09-23 21:11:20 +02:00
LibretroAdmin b177f08b4d wdmks: the WaveRT loop plays silence when the audio runs out
Pausing a core on the wdmks driver, or anything else that stops the
frontend writing - the menu with the core silent behind it, a stall -
played the last few milliseconds of audio round and round at full
level for as long as it lasted.

A WaveRT pin plays a mapped loop; unlike a packet pin it has no notion
of running out. The frontend writes a fade to silence on a pause and
then nothing, on the understanding that a device with nothing to play
plays nothing. The loop kept what it held and the hardware kept
reading it, so the tail of the fade and whatever came before it in
the loop repeated at the loop's own rate until writes resumed.

Now every byte the cursor passes is cleared as soon as it has been
played, so from then until it is written again it holds silence, and
whatever the hardware reads past the end of the audio is silence.

Two more things fell out of the same bookkeeping:

The two positions alone could not say whether a loop with the cursors
together was full or empty. The model took it for full, so at start
the first audio was only ever placed behind the hardware as it moved
off, one loop late; and after the hardware overran the write cursor
the next write went in behind it too, to be played a loop late for
good. Room is now the loop less a count of what has been placed and
not played, and when that count shows the hardware past the write
cursor - or the cursor could not be watched round - the write cursor
is moved a small margin ahead of it.

start() takes the pin through ACQUIRE, which puts the hardware back at
the top of the loop; the model now starts over from the first sample
after it rather than believing a write cursor left for the old one.

samples/audio/wdmks_loop compiles the driver whole and drives the loop
by hand on both the caller-driven path and the refill thread's pump,
under wine; it fails on the previous driver at "the audio ran out: the
whole loop is silence, not its tail again", among others, and passes
now.
2026-09-23 21:06:10 +02:00
libretroadmin 535b272e73 overlay: ledN_map hides the image at its slot, whatever the desc does
ledN_map reaches the image at the slot it names whatever the desc
there does when pressed, as it did before the _led naming: an LED
overlay may put its lights on keys or buttons, and a config
calibrated on such a pack keeps working. A pack that names its LED
images with _led is still not mapped, and a hidden image still stays
hidden while its desc is pressed.

overlay_visibility_bounds_test pins every mapped slot, control or
"nul", as hidden while its LED is off; overlay_led_hidden_test now
expects the mapped "up" square hidden, pressed or not.
2026-09-23 20:40:22 +02:00
LibretroAdmin d8ca8d978a gfx_thumbnail: draw dimensions work in locals
gfx_thumbnail_get_draw_dimensions() unpacks the thumbnail's size once,
folds the core-aspect correction into one ratio that is 1 when it does
not apply, and writes the two results once at the end rather than
reading them back through the pointers between steps. Bit-identical
results. vulkan_init_readback() reads the viewport size once.
2026-09-23 20:20:30 +02:00
LibretroAdmin aed07d40dc video: Y+X integer scaling multiplies the frame width, not the aspect width
With Integer Scale Axis set to Y + X (or any of the modes that touch X),
the horizontal multiplier was applied to the aspect-corrected width
rather than the frame's own width. A 256x224 Snes9x frame with its core
aspect of 64:49 is 293 wide after correction, so at 1080p the scaler
picked 293 x 4 = 1172 wide over 224 x 4 = 896 high: a 4.58x horizontal
scale, pixel columns alternating 4 and 5 wide, and the fraction the
statistics overlay shows as Scale X/Y. It now picks 1280x896, 5x/4x.

The "reset width to exact width" step used to read the frame cache
fields directly. When those became a local snapshot (69efa8a894) the
reset started reading content_width/content_height, which by that point
hold the aspect-corrected width and the rotated height. The frame's
size is now kept in its own pair and the reset reads that; rotated
content takes the frame height as its width, as it did before.

Driver-agnostic: the scaler is in video_driver.c, so d3d11, Vulkan and
the rest all showed it.

Regression test: samples/gfx/integer_scale_axis runs the scaler out of
video_driver.c (extracted by awk, as asio_clock does) over a sweep of
viewports, frame sizes, axis and scaling modes, rotated and hi-res
content, and checks each drawn size is a whole multiple of the frame
size; the first check is the reported 1080p 256x224 case. On the old
code it fails 386 of 778 checks, reporting 1172x896 for the report
case; on the new code all 783 pass. Wired into CI Linux samples/gfx
under ASan + UBSan.

Reported-by: sonninnos
2026-09-23 20:11:44 +02:00
LibretroAdmin 7ab06c244e gfx, menu: a size parameter is unpacked once
Functions that unpacked the same dims parameter many times read its
halves into two locals at the top instead. Only where the object code
came out the same size or smaller.
2026-09-23 20:06:38 +02:00
LibretroAdmin 4d8511960b gfx: a size word repacked from its own halves is the word
VIDEO_SCALE_PACK(VIDEO_SCALE_W(x), VIDEO_SCALE_H(x)) is x; ten
places that built it that way take x.
2026-09-23 19:57:10 +02:00
LibretroAdmin b93becffd5 vulkan: render targets and the overlay take the size word
vulkan_init_render_target() and vulkan_render_overlay() take the
size as the VIDEO_SCALE_PACK word every caller already holds.
2026-09-23 19:57:06 +02:00
LibretroAdmin e0c314236e vulkan: the display mode search unpacks each size once
The wanted size is unpacked once, and a mode is taken on one path
after the two tests that can turn it down.
2026-09-23 19:45:55 +02:00
LibretroAdmin 41a9193557 vulkan: the swapchain and surface are asked for by size word
vulkan_create_swapchain() and vulkan_surface_create() take the wanted
size as one VIDEO_SCALE_PACK word, and so does KHR_display's mode
search, from vulkan_display_surface_info through the chosen mode. The
context drivers pass the words they hold rather than their halves.
2026-09-23 19:40:56 +02:00
libretroadmin d2a55553f4 CI gl-harness: drop the no-display step
The step started the build with no display and required exit status 1.
On the runners it fails at once with nothing in the run log, on every
driver and sanitizer, and the same run passes on a runner-shaped build
elsewhere, so it tests the runner rather than the code. It goes; the
glcore teardown guard it was written for stays in gl3.c.
2026-09-23 19:22:59 +02:00
LibretroAdmin ce44f46d77 gfx: set_resize takes the size word
A context driver's set_resize takes the output size as one
VIDEO_SCALE_PACK word, which every caller already holds; the D3D8 and
D3D9 drivers' own set_resize helpers take it the same way. The X11
contexts log the size as unsigned.
2026-09-23 19:17:38 +02:00
libretroadmin a58b0edc26 x11: the pointer poll reads the pointer from events
With XInput 2, the driver's own connection takes the master pointers'
motion, button and crossing events on the window, and the poll reads
each pointer's position, buttons and whether it is inside from them,
so it sends no request. A pointer outside the window reads nothing
held, a drag out of it included. That connection holds the window's
button events, so it latches the wheel and buttons 8 and 9. A grabbed
pointer is warped back on the same connection, and a position older
than the warp is ignored. Each pointer is queried once at init; a
server that refuses the selection, or no XInput 2, leaves the query.

samples/input/x11_key_poll becomes x11_input_poll and drives the
pointer too: buttons, the wheel, a drag, a new driver over a held
button, and grabbed motion across a warp held behind a server grab.
2026-09-23 19:16:37 +02:00
libretroadmin 233b871543 x11: the keyboard poll reads keys from events
x_input_poll() reads the keyboard from key events on a connection of
the driver's own, drained at the poll, so it sends no request and
never waits behind what the video thread presents through the shared
connection; keys still read as of the poll. KeymapNotify after each
FocusIn replaces the key vector and a focus change clears it, except
under another client's grab, which keeps the window's focus. Detectable
auto-repeat keeps a held key from ever arriving as a release. Without
that connection the poll queries the server as before.

samples/input/x11_key_poll drives the driver through XTEST on Xvfb:
no requests over 200 polls, a key held before init, auto-repeat, focus
changes, a keyboard grab and the fallback.
2026-09-23 18:55:27 +02:00
LibretroAdmin 539a87bdb8 vulkan: the filter chain's textures carry one size word
vulkan_filter_chain_texture keeps its size as one VIDEO_SCALE_PACK
word, so the driver's texture, framebuffer and history sizes pass
through as words. slang_pass_get_output_size(),
slang_framebuffer_set_size() and the texture-size semantic builders
take the words they were unpacked from.
2026-09-23 18:44:47 +02:00
LibretroAdmin a2fad5f361 X11: detect XWayland by its extension, not its output names
The Mutter path in dispserv_x11 (17c7c6a) recognised XWayland only by
RandR output names starting with XWAYLAND. That holds for older
XWayland and for compositors that do not name their outputs, but under
Mutter XWayland takes each connector's name - DP-1, HDMI-1, Meta-0 -
like any X server. On a GNOME desktop the check never matched, the
XRandR path ran, and the Screen Resolution list stayed XWayland's:
every mode at the desktop's current rate, no 60 Hz on a 240 Hz panel.

XWayland 23.1 and later advertise the XWAYLAND extension, which is
what xrandr tests for its "running against an Xwayland server"
warning. That is now checked first, the name prefix kept as the
fallback for older XWayland. A real X server has neither, so it still
never talks to D-Bus.

Regression test: a new mutter_displayconfig case runs dispserv_x11 on
the XWayland of a real headless Mutter (one 2560x1440@60 virtual
monitor, output named Meta-0) and requires Mutter's list - one mode at
exactly 60.000 Hz - rather than XWayland's CVT list. Against the
previous dispserv_x11 it fails with XWayland's 33 entries; mutter joins
the step's packages.
2026-09-23 18:41:55 +02:00
LibretroAdmin 318e92d61b vulkan: a texture's size is one word
struct vk_texture keeps its size as one VIDEO_SCALE_PACK word. The
readback staging check compares it with the viewport's word directly;
elsewhere the halves are read where Vulkan wants them apart.
2026-09-23 18:34:56 +02:00
libretroadmin ad5a999ee8 CI samples: winraw_abs_pos runs in the input job
samples/input/winraw_abs_pos is cross-built with mingw-w64 and run under
Wine, and no workflow named it, so the job for unclaimed samples picked
it up with only build-essential installed and failed on a missing
compiler. The input job now runs it, installing mingw-w64 and Wine when
the runner lacks them, which also takes it off the unclaimed list.

The unclaimed job installs the same two the first time a leftover
sample's Makefile uses them, so the next Windows sample without a home
runs there instead of failing for want of a compiler.
2026-09-23 18:26:39 +02:00
libretroadmin 71c885ef27 CI gl-harness: the no-display step pins the null GL context
Left to probe with no display, the context the GL drivers find depends
on the runner rather than on the code: a build with SDL2 brings up its
offscreen EGL backend on llvmpipe, and a VM with a DRM device can bring
up KMS. The step then runs a live driver instead of the refusal it is
there to test, and its exit status follows the image.

The step sets video_context_driver to null, so both drivers are handed
the null context on every host, and requires the log to name it before
checking the exit status. With the teardown guard in gl3.c reverted the
glcore run still faults with 139.
2026-09-23 18:26:36 +02:00
LibretroAdmin 289dcd9124 switch: the original size is one word; unread sizes go
switch_video_t keeps the size o_size shows the frame at as one
VIDEO_SCALE_PACK word. Its width/height, and the context's
width/height, native_window and resize, were written and never read;
they go, and the context crops from the size word it already has.
check_window's locals are declared C89-first.
2026-09-23 18:21:25 +02:00
LibretroAdmin 3c0df2d8da vulkan: the swapchain's size is one word
The Vulkan context keeps its swapchain size as one VIDEO_SCALE_PACK
word, and khr_display its own size likewise; the retained backbuffer
and the window size compare and copy the word rather than repacking
its halves.
2026-09-23 18:11:43 +02:00
LibretroAdmin 17c7c6a393 Screen Resolution under GNOME through Mutter's DisplayConfig
On GNOME's Wayland session RetroArch's X11 driver runs on XWayland.
XWayland lists every mode at the desktop's current rate - a 240 Hz
panel shows no 60 Hz mode - and a "switch" only scales the fullscreen
window; the real modes belong to the compositor. The Wayland driver
had no resolution list at all. Under Mutter no X11 or Wayland protocol
reaches the real modes; its org.gnome.Mutter.DisplayConfig D-Bus
interface (what GNOME Settings uses) does.

gfx/common/mutter_displayconfig.c (HAVE_DBUS only) reads
GetCurrentState and applies ApplyMonitorsConfig:

- The list is the target head's real modes, one entry per size and
  rate (a fixed and a variable rate twin are merged), sorted, the
  scanned-out mode current.
- A switch picks the listed mode of that size nearest the rate, the
  same rule as the XRandR path; a zero size keeps the current one (the
  refresh rate autoswitch). It is applied as a temporary configuration:
  not saved to monitors.xml and no "keep these settings?" prompt.
  Every other monitor keeps its mode, position, scale and transform;
  every monitor keeps its colour mode (HDR), RGB range and
  underscanning; a mirror switches all its monitors or none; heads to
  the right of or below the switched one move with its new size; the
  layout mode goes along only where Mutter allows choosing it.
- The target head is the connector (wl_output v4 name), else the head
  under the RetroArch window (XWayland root coordinates are Mutter's
  logical layout), else the monitor index, else the primary.
- Each call opens and closes its own private session bus connection,
  bounded by a 2 s timeout. With no bus address and no
  $XDG_RUNTIME_DIR/bus it does nothing, so no bus is ever autolaunched.

Backwards compatibility - every existing path behaves as before:

- Builds without HAVE_DBUS compile exactly the previous code.
- dispserv_x11 only asks Mutter when the output is XWayland's
  (XWAYLAND<n>); a real X server never talks to D-Bus. On XWayland
  without Mutter (KDE, sway, ...) XWayland's own list and switch are
  used as before. Only when Mutter answers is its answer final.
- dispserv_wl gets set_resolution, get_resolution_list and get_flags
  in HAVE_DBUS builds. A new display server flag,
  DISPSERV_CTX_NO_RESOLUTION_LIST, lets it report "nothing to list
  right now": set when Mutter was not on the bus at init, and
  video_display_server_has_resolution_list() then answers false, so
  the Screen Resolution entry and the refresh rate autoswitch stay off
  on non-GNOME Wayland exactly as before. No existing server sets the
  flag, and its bit is not DISPSERV_CTX_MODELINE's.

Checked against a real headless Mutter 46: GetCurrentState parses and
ApplyMonitorsConfig is accepted.

Regression test samples/gfx/mutter_displayconfig (ASan + UBSan, new
Linux-samples-gfx step): mock_mutter.py carries Mutter's signatures and
its ApplyMonitorsConfig checks (serial, connectors, modes, scales,
every lit monitor configured) on two monitors - a 240/120/60 Hz HDR
panel and an underscanned one beside it. Cases: no session bus and a
bus without Mutter (unavailable, nothing autolaunched); the list, a
60 Hz switch with every other setting carried over, rate only, whole
hertz, a smaller mode with the neighbour moved, another head by
connector, unlisted size and rate refused without an apply; dispserv_x11
on a real Xorg dummy server with Mutter on the bus (XRandR list, Mutter
never asked); dispserv_x11 on Xwayland under a headless Weston with
Mutter (Mutter's list and switch) and without (XWayland's list as
before); dispserv_wl with Mutter (list, switch, not flagged) and
without (flagged, no list, as before).
2026-09-23 17:59:15 +02:00
LibretroAdmin 8cee4866b1 vulkan: the filter chain is handed the texture size word
vulkan_filter_chain_create_info carries the largest input as the
driver's own VIDEO_SCALE_PACK word rather than two axes the chain
packs again.
2026-09-23 17:40:17 +02:00
LibretroAdmin 987b8c1d12 gfx_thumbnail: an animation job's size is one word
The decode job keeps its surface's size as the VIDEO_SCALE_PACK word
the surface was made with.
2026-09-23 17:40:13 +02:00
LibretroAdmin 9477335429 gl1, gl2: the font atlas texture's size is one word
The raster font keeps its power-of-two texture size as one
VIDEO_SCALE_PACK word; the atlas upload unpacks it once.
2026-09-23 17:22:29 +02:00
LibretroAdmin 19427174a8 X11: implement the Screen Resolution display server callbacks
dispserv_x11 left set_resolution and get_resolution_list NULL, so
video_display_server_has_resolution_list() was false on X11: the
Settings > Video > Output > Screen Resolution entry was hidden, and
the automatic refresh rate switch and its PAL threshold never ran.

Both are implemented on XRandR 1.2, gated on HAVE_XRANDR like the
rest of the RandR code:

- get_resolution_list lists the modes of one head - the one under
  the RetroArch window, or under the Xinerama screen
  video_monitor_index names, else the primary output, else the first
  lit one (the rule modeline_open already uses). The field rate is
  computed from the timing with interlace and doublescan accounted
  for, the same size and rate listed under two names is merged into
  one entry, exactly the scanned-out mode is marked current, and the
  list is sorted by size then rate.

- set_resolution switches that head to the listed mode of the asked
  size nearest the asked rate (within half a hertz, or matching the
  whole-hertz menu label), progressive over interlaced. A zero size
  keeps the current one, which is how the refresh rate autoswitch
  asks. The framebuffer grows before the crtc takes a larger mode
  and shrinks to the lit area after, so each step is a configuration
  the server accepts; other heads are left where they are, the DPI is
  kept, and the root window geometry is read for the current size
  rather than Xlib's cached DisplayWidth(). X errors are trapped, a
  failed switch puts the framebuffer size back, and the kept refresh
  rate is invalidated. An unlisted size is refused and changes
  nothing. No libm: the driver still links without it.

Regression tests:

- samples/gfx/display_servers_x11_live (Xorg dummy server, ASan +
  UBSan, already run by Linux-samples-gfx): the list must describe
  the lit head with one current entry, in order, without
  duplicates; switches to a smaller mode, a larger mode, a rate only
  (size 0), a whole-hertz rate, and back must land on the wire with
  the framebuffer following; an unlisted size must fail and change
  nothing. Fails on the old tree (callbacks NULL).

- samples/gfx/display_servers (stubbed RandR): every failing query
  and a disconnected head give an empty list and a refused switch,
  with no crtc configured, no leak and no bad free.
2026-09-23 17:18:40 +02:00
LibretroAdmin 69d740361c CI PS4: build in the orbis image with docker run
The orbis image is Alpine 3.15, whose musl cannot load the runner's
Node 24, so checkout and upload-artifact run on the host and the
dependency install, build and toolchain version steps run inside the
image through docker run.
2026-09-23 17:03:37 +02:00
LibretroAdmin 59e6f166cd gfx: six more context drivers keep one size word
opendingux, mali and videocore keep their size as one VIDEO_SCALE_PACK
word and hand it back as it is. vivante, vita and orbis stored a size
nothing read; those fields go, with vivante's unused default size and
EGL format and mali's unused set_video_mode locals.
2026-09-23 16:57:44 +02:00
libretroadmin d69c11b20e qb: find PipeWire and SPA headers when pkg-config is absent
Without pkg-config, check_val falls back to a plain link test and takes
the include flags from the directories it is given. check_lib now accepts
a list of include subdirectories, each of which has to exist for the
library to be enabled, and PipeWire passes both pipewire-0.3 and spa-0.2.
A system that has the library but not the headers ends up with PipeWire
disabled rather than enabled with no include path, and PIPEWIRE_STABLE
takes its 1.0.0 floor from PW_CHECK_VERSION in the headers.

A CI lane configures with PKG_CONF_PATH=none and builds to completion,
checking that PipeWire is enabled with both include directories.
2026-09-23 16:46:30 +02:00
LibretroAdmin 58dc63615a audio: the resume mark has one writer
The main thread publishes the resume mark as one word, a sequence and
an armed bit, and is its only writer; the consumer records the
sequence it acted on instead of clearing the flag, and takes the
position between two reads of the mark that agree. A pause and resume
landing while the consumer reads can no longer have their mark
cleared unseen, so the resumed audio keeps its ramp.

samples/audio/pipeline_fade_mark runs the shipping publish, pending
and clear functions with a pause and resume delivered in every gap of
the consumer's read.
2026-09-23 16:39:59 +02:00
LibretroAdmin 5108ed0a2c d3d11, font renderers: the retained buffer and the glyph cell are words
d3d11 records the back buffer size its copy was made at in one word,
as the d3d12 driver does. Both font renderers hold the cell every
glyph is rendered into the same way, so the atlas layout, the copy
clamp and the blank-glyph clear all read their axis from one place.
2026-09-23 16:39:41 +02:00
LibretroAdmin 0fbc22f324 android: the drive list's volume loop is C89
Its counter is declared with the function's other locals.
2026-09-23 16:16:05 +02:00
LibretroAdmin 7ff2befb44 android: the content rectangle's size is one word
onContentRectChanged() publishes the size as one VIDEO_SCALE_PACK
word, so the Vulkan context never pairs one change's width with
another's height, and check_window() takes the changed flag with an
exchange, so a change raised while it reads is not cleared unseen.
The context keeps its own size as one word too.

samples/gfx/android_content_rect runs both shipping functions on the
host, taken out of their files at build time, with the UI thread's
change delivered in every gap between the reader's operations.
2026-09-23 16:16:01 +02:00
LibretroAdmin d1fb122b6e winraw: an absolute report's position is one word
The report handler publishes an absolute report's x and y as one
VIDEO_POS_PACK word, so winraw_poll() on the main thread reads a
position a report gave rather than one report's x with the next
report's y.

samples/input/winraw_abs_pos runs the real driver under Wine and
delivers a second report between poll's loads.
2026-09-23 16:08:50 +02:00
libretroadmin b07619da1e glcore: a context that never came up tears down without GL calls
With no display only the null GL context is found, and the glcore
driver refuses it. Its teardown now touches the vertex array object
only when one was made, so the refusal reaches the frontend as a
failed video init and an exit with an error, as the gl driver's does,
instead of a call through a GL entry point the null context never
resolved.

The llvmpipe harness job starts the build it tests with no display,
directly and threaded, on both GL drivers, and requires exit status 1.
2026-09-23 16:07:02 +02:00
Jesse TalaveraandClaude Opus 5 25116c7774 libretro-db: write the tags field in c_converter (#19613)
Explore already reads "tags" from database entries, but c_converter had
no mapping for it, so a tags field in a DAT never reached the .rdb.

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-09-23 16:06:24 +02:00
LibretroAdmin e31b6de5a6 vulkan, sdl2: the readback record and the font atlas are sized by word
Each readback slot records the extent its copy was taken at in one
word, and the SDL2 font holds its atlas texture's size the same way,
so the glyph scale and the upload stride read their axis from one
place.

The two padded structs in vulkan's uniform block stay as they are:
the shaders read that layout.
2026-09-23 15:58:17 +02:00
LibretroAdmin f19921bf81 coreaudio: the worst underrun is one word
The deepest shortfall and the ring's fill in that same callback share
one atomic word, in frames, 16 bits each: one store records both and
the teardown log reads them together.

coreaudio_write runs underruns of different depths and checks the
word keeps the deepest with its own fill.
2026-09-23 15:58:04 +02:00
libretroadmin bd710babc3 x11: the grabbed mouse asks the server only for the pointer
With the mouse grabbed, the X input driver takes the window's centre
from the size the event pump records and sends the warp back to it
without waiting: the next poll's pointer query follows the warp on
the connection, so it reads the warped position. A poll costs the
keymap query, the pointer query and the warp. The recorded size is
one word, written by the pump and read by the poll on the runloop
thread; the server is asked for the size only for a window the pump
has not seen.

The threaded video harness moves the pointer from a connection of its
own between polls, directly and under the wrapper, and requires the
exact motion from each poll and no more than those three requests.
2026-09-23 15:49:52 +02:00
LibretroAdmin 29b85f70ab video_thread: the window's answers are one word
alive, focus, presentable and has_windowed are bits of one atomic
word the video thread publishes with a single store after each frame.

The window answers lane drives each answer on its own through a
swapped driver and reads it back on the main thread, so an answer
landing in another's bit, or one that does not move, fails.
2026-09-23 15:49:37 +02:00
LibretroAdmin 1b9b4a1d90 switch: the video driver's sizes are words
switch_video_t holds the frame size the scaler was last built for,
the window size the hardware scaler asks the compositor for, and the
menu texture's arriving and on-screen sizes one word each, so the
rebuild test is a single comparison and the two nwindowSetDimensions
calls read their pair from one place.

Five declarations in this file move to the top of their block, two of
them out of a for statement, so it builds as C89.
2026-09-23 15:49:23 +02:00
LibretroAdmin c94e465bbe sunxi: the emulator framebuffer's size is one word
The driver keeps the size the core renders at in src_dims, and
sunxi_setup_scale takes it as that word. Four menu fields go with the
change: menu_rotation, menu_width, menu_height and menu_pitch were
declared and never touched, and menu_active is the only one the
driver reads.

The two __disp_ rect types above stay as they are: they are the
kernel's disp ioctl layout, not ours.
2026-09-23 15:49:20 +02:00
LibretroAdmin 926070761d drm: the plain DRM driver's sizes are words
A dumb buffer's pixel size sits beside the stride the scanout walks,
and the driver holds the surface's internal size, the mode the
display is running and the size the core last handed over the same
way. drm_surface_setup takes the size as the word its menu caller
already had.

struct drm_rect went with them: nothing has ever used it.
2026-09-23 15:25:44 +02:00
LibretroAdmin e94271d4e4 d3d12: the retained back buffer and the software framebuffer are sized by word
The copy of the last presented back buffer records the size it was
made at, and the software framebuffer the size it was laid out for,
one word each, so each recreate test is a single comparison against
the size in hand.

The OutputSize pair in the uniform block stays two floats: the shaders
read it as a float2, so its layout is not ours to change.
2026-09-23 15:25:41 +02:00
LibretroAdmin 8520f2471a matrix: lanes for the shared Wayland unit
gfx/common/wayland_common.c is what both Wayland contexts call into
and was the one Wayland unit no lane compiled. It gets three, for the
plain build, the libdecor build and the backport build, each carrying
the declaration-after-statement gate the PSP lane already uses.

The libdecor lane defines HAVE_DYLIB alongside HAVE_LIBDECOR_H: the
function pointers that half calls through are declared under both, so
a lane naming only the first compiles a file with no libdecor calls
left in it.
2026-09-23 15:25:37 +02:00
LibretroAdmin 39fbd1ca2c vulkan: the vertex colour is four bytes
struct vk_vertex carries its colour as R8G8B8A8_UNORM, packed by
rgba8_pack() in gfx/common/rgba8_pack.h: a vertex is 20 bytes instead
of 32, and the menu's per-vertex bake packs with one SSE2 or NEON
sequence rather than four float stores. The font and quad paths pack
their colour once per string or quad.

samples/gfx/rgba8_pack holds the vector path and the scalar one to
the same reference: every 8-bit level, both sides of every rounding
boundary, saturation past either end, infinities and NaN.
2026-09-23 15:22:43 +02:00
LibretroAdmin 8394074dd3 CI: overlay_led_hidden_test runs in the overlay on-screen workflow
The workflow runs it after the menu-mouse test, installs libxtst6 for
its XTEST key presses, and also triggers on led/drivers/led_overlay.c.
The overlay_visibility_bounds_test step in the tasks samples workflow
describes what the test now covers: input_overlay_alpha.c's LED hiding
and its per-poll alpha pass.
2026-09-23 15:19:34 +02:00
libretroadmin 04cc6c998d x11/vulkan: the swapchain presents on an X connection of its own
The X Vulkan context opens a second connection to the server for the
swapchain and hands it to the XCB surface; the window stays the
frontend's. A software WSI writes each frame into its connection as
image data, and on the frontend's connection every request the
frontend makes - the input driver's keymap and pointer queries, the
event pump - waited behind that frame on whichever thread presented.
The connection closes after the swapchain and surface; if it cannot
be opened the frontend's is used. vulkan_surface_create() takes the
XCB connection itself.

The threaded video harness counts the requests on the frontend's
connection over 60 presented frames with the input poll left out and
requires none. The compile matrix builds the context with XCB, as
the real build does.
2026-09-23 15:19:07 +02:00
libretroadmin 51e356bc3c samples/x11_refresh_rate: count requests once the reader has settled
The reader thread asks for the rate on g_x11_dpy, the connection the
requests are counted on. After the switch its ask can still be reading
the server when the count starts, or can withdraw a rate it kept across
the pump's invalidations, and either lands requests inside the count.
The count now starts only once an ask that began after the pump has
finished, so every earlier ask is done and the kept rate is final.
2026-09-23 15:07:33 +02:00
libretroadmin 8eb6034dd0 x11: keep the display's refresh rate until the display changes
The X display server reads the refresh rate from the server once per
display change and answers from memory in between, so a core polling
the throttle state every frame, or the menu showing the rate, sends
nothing to the server. On g_x11_dpy it selects RandR screen, crtc and
output changes on the root window; the event pump drops the kept rate
on each of them, and the display server drops it after its own mode
sets. The rate is kept only once the pump drains that connection, and
a reader that raced a change withdraws what it kept. A NULL output
from XRRGetOutputInfo is skipped, and opening the display no longer
writes the display server's flags, which two threads ask at once.

samples/gfx/x11_refresh_rate runs the real pump and display server on
an Xorg dummy server against mode switches from another client, with
a second thread asking throughout, under ASan+UBSan and TSan.
2026-09-23 14:49:47 +02:00
LibretroAdmin 168dadd291 audio: one word for the device-clock estimate
A driver's device-clock estimate and whether there is one share a
single int, AUDIO_CLOCK_PPM_NONE until the first estimate: one store
publishes it, one load reads it, and a reset landing inside a read can
no longer pair an old flag with the reset's value (ASIO, JACK, Pulse,
CoreAudio, WDM-KS, 3DS DSP).

asio_clock runs the driver's own publish, reset and reader, taken from
asio.c at build time, with a reset between the reader's loads;
live_drivers checks Pulse and ALSA report no clock before any audio.
2026-09-23 14:30:25 +02:00
LibretroAdmin 13cd2a02e2 video_thread: suppress_screensaver reaches the driver
Send the request to the video thread, which owns the window, and
return the driver's answer rather than a constant.
2026-09-23 14:05:58 +02:00
LibretroAdmin e308425da8 wayland: the surface, its buffer and the floating size are words
gfx_ctx_wayland_data_t holds the surface's size, the buffer behind it
and the size to restore when the window floats again one word each,
and the latched xdg_toplevel configuration holds the size the
compositor asked for the same way. Both configure paths scale one
axis into each half of the buffer word in a single pack.

Four declarations in this file move to the top of their block so it
builds as C89 under the libdecor path as well as without it.
2026-09-23 12:47:51 +02:00
LibretroAdmin 5b90ce9a16 wayland: an output's mode is one word
output_info_t carries the mode the output is running in dims, which
is the word both readers of it were building anyway. The physical
size the geometry handler also stored goes with them: nothing read
it, and the display server keeps its own copy for the DPI it reports.
2026-09-23 12:47:48 +02:00
LibretroAdmin 84e54a6f4c emscripten: the canvas size is one word, read and written whole
The browser's canvas size is stored as one packed word, so a read on
the main thread can no longer pair a width from one resize with a
height from another; platform_emscripten_get_canvas_dims returns it
and platform_emscripten_set_canvas_size takes it. The EGL and WebGL
contexts keep their framebuffer size the same way. The JavaScript
side keeps its two numbers.
2026-09-23 12:47:34 +02:00
LibretroAdmin 2d075314fb Sync to Exact Content Framerate: keep vsync within the skew tolerance
A content rate slightly past what the display presents at the chosen
interval - a 60.0988 Hz core at swap interval 2 on a 120 Hz panel, or
60 Hz content on a 59.94 Hz VRR ceiling - is paced by vsync with audio
skewed as it is without VRR, rather than forcing the loop nonblocking.
Only a content rate beyond audio_max_timing_skew keeps its own rate
with vsync off.

The decision is runloop_sync_plan_for() in runloop.h, pinned by a
table and an invariant sweep in samples/runloop/pacing. Fixes #19600.
2026-09-23 12:38:19 +02:00
LibretroAdmin 18b806e732 metal: no emulated swap interval while display sync is off
With display sync off a repeated present is not held for another
display frame, only presented again unpaced. set_nonblock_state now
takes the interval to 1 in nonblocking mode, as Vulkan's does.
2026-09-23 12:38:14 +02:00
LibretroAdmin b5096ce522 x11: answer the per-frame window poll from events
x11_check_window() and x11_has_focus() run on the frame path and must
not wait on the server. Keyboard focus is tracked from FocusIn/FocusOut
on the window (grabs ignored), so the GLX, EGL and Vulkan X contexts
select FocusChangeMask. The window size is recorded when the window
maps, since without a window manager no ConfigureNotify follows; it is
cleared with the window, so a new window never reports the old size.

The main thread no longer shares a round trip with a software Vulkan
WSI presenting a whole frame through the same connection.
samples/gfx/x11_frame_poll runs the poll on Xvfb and requires it to
send no requests, with size and focus still following the window.
2026-09-23 12:36:11 +02:00
LibretroAdmin a4d56f5a34 gxm: the Vita driver's sizes are words
The display mode's pixel size sits beside the stride it scans out
with, and the driver holds the frame the core hands over, the size it
presents at, the menu frame and an overlay image's size the same way.
An overlay's tex_w and tex_h stay floats: they are a fraction of the
image, not the image.
2026-09-23 12:35:57 +02:00
LibretroAdmin 9a0613340d tools: drop a checked-in bytecode cache
tools/__pycache__ is written by the Python checkers whenever they run,
so a copy tracked in the tree shows up as a modified file after any
run of them, and the one that was there was built by a different
interpreter version than the tree's lanes use. .gitignore keeps it
out.
2026-09-23 12:35:52 +02:00
LibretroAdmin 861f6c9c53 tools: a lane for the readers a removed struct member leaves behind
The members a change removes, minus the ones something still declares,
against every reader in every language the tree is written in. A
reader the Linux compilers never see - an Objective-C driver, anything
behind a platform #ifdef - is named by file and line here rather than
by whichever platform job happens to build it.

The checker carries fixtures of its own: a member removed with its
readers, one left behind, one that merely moved, one whose name
another struct still declares, a rename, a name appearing only in a
comment or a log string, a declarator list, and a name too common to
reason about.
2026-09-23 12:21:09 +02:00
LibretroAdmin b8c64ac8f3 cocoa: the window geometry handlers read the packed origin
ui_cocoa reads and writes settings->uints.window_position_pos, so a
window placed on a display left of or above the main one keeps the
negative origin AppKit gives it.
2026-09-23 11:52:41 +02:00
libretroadmin c04bc789d6 Clear warnings in thread-less, microphone-less and GL-less builds
runloop_msg_queue_off_main() is built only with HAVE_THREADS, like its
callers. WASAPI's error-string helper is built only with
HAVE_MICROPHONE, the one path that calls it, and the driver casts
GetProcAddress straight to its function-pointer types, so it builds in
the Windows C89_BUILD lane. The ffmpeg core's slot helpers for the GL
path are built only with OpenGL, and its channel-layout declaration
sits at the top of its block. The IIR filter's RIAA CD case marks its
fall into the high shelf, and the ffmpeg_live sample's GL frame
callback has the libretro signature, which GCC 14 and later require.

The compile matrix installs mingw-w64, which turns its Direct3D lanes
on, and compiles WASAPI with the C89 flags both with and without the
microphone, treating unused functions as errors. That lane compiles
for real rather than with -fsyntax-only, which never reports unused
static functions.
2026-09-23 06:36:16 +02:00
LibretroAdmin 6a9e8a99a6 menu: the draw helpers take the screen size once, as the word it is
ozone_draw_icon and xmb_draw_icon already take video_dims and read the
screen height from it; the width and height they were also handed
are gone. xmb_draw_text, xmb_draw_dark_layer and the MaterialUI
scrollbar helpers take the packed size their callers hold instead of
its two halves, and xmb_frame reads video_info->dims where it rebuilt
it from its own halves. The locals that only fed those arguments go
with them.
2026-09-23 06:36:00 +02:00
LibretroAdmin 12438582fd rgui: the thumbnail box downscaler takes and returns its size as one word
rgui_downscale_box takes the source size packed and hands the result
back the same way; a source too large to pack goes straight to the
resampler.
2026-09-23 06:35:57 +02:00
LibretroAdmin 03fa7238b6 win32: the window's own position and frame size are words
The module's window state keeps its origin and its frame size one
word each, and a pos_set flag says whether an origin has arrived yet,
so the case where Windows chooses the position is a flag rather than
a sentinel coordinate sitting in the same field as a real one.
Carrying the origin between the setting and the window is now a
single assignment either way.
2026-09-23 06:35:37 +02:00
LibretroAdmin ce864e8977 settings: the window's origin is one signed word
window_position_pos holds where the window sits in VIDEO_POS_PACK's
layout, and its two config keys are signed rows over the halves of
it, so a display left of or above the primary one saves the negative
coordinate it actually has and reads it back the same way. The keys
keep their names and a non-negative position carries over from an
existing config unchanged.
2026-09-23 06:35:33 +02:00
LibretroAdmin dc6f2b8542 d3d9: a shader pass's last frame size is one word
Each pass holds the frame size it last built vertices for in
last_dims, and the ring of previous-frame sizes is one array of those
words rather than a pair of parallel arrays, so carrying a size
between the ring and pass zero is a single assignment.
2026-09-23 06:02:43 +02:00
LibretroAdmin 2377645933 d3d: a shader pass's render target size is one word
LinkInfo carries the size of the texture a pass renders into as
tex_dims, and set_pass_size in both d3d9 chains takes that word in
place of two axes, so the test for whether a pass has to be
respecified is a single comparison against the size the caller
computed.
2026-09-23 06:02:39 +02:00
LibretroAdmin b93d6e03a2 cocoa: cocoa_common.h keeps its CRLF line endings
A rewrite of the output-size query saved the header with LF endings;
it goes back to the CRLF it had.
2026-09-23 06:02:27 +02:00
LibretroAdmin d12e50b716 camera: a driver is opened at one packed size
camera_driver_t init takes the requested frame size as one word, which
the frontend packs from the setting or the core's request. The FFmpeg
driver keeps its requested and target sizes that way, V4L2 the size
its device negotiated, and the PipeWire format builder takes the
size packed. The FFmpeg live test checks that frames arrive at the
size asked for.
2026-09-23 06:02:24 +02:00
LibretroAdmin df41a367a6 companion: rectangles and dock rows are an origin and a size word
companion_rect_t is an origin packed with VIDEO_POS_PACK and a size
packed with VIDEO_SCALE_PACK, and companion_dock_state_t holds a dock
row's position and size the same way; the rows read and write as
before. companion_place_window takes the needed and minimum sizes
packed. The dock layout works in signed locals and packs each
rectangle once, clamping the content width and a pane's height to
zero before they are stored. The Win32, Cocoa and Qt companions fill
and read the packed rectangles.
2026-09-23 05:51:05 +02:00
LibretroAdmin 53b1b1ec7f d3d8: the driver's own size and origin pairs are words
The soft scissor keeps its rect as an origin word and a size word, so
the rect the menu hands scissor_begin is stored as the word it
arrives in. The render chain holds the frame size it last built
vertices for and the size of the texture behind them the same way,
and the font's atlas mirror does too, which makes the respecify test
a single comparison against the size the atlas reports.
2026-09-23 05:50:50 +02:00
LibretroAdmin a2f9b67aa0 d3d: an overlay's texture size is one word
overlay_t carries the size of the texture behind it in tex_dims, so
the menu overlay's size test in every d3d driver is a comparison
between the word the frame arrives as and the word the texture was
made from, rather than a pair of axis comparisons either side of it.
2026-09-23 05:50:47 +02:00
libretroadmin 34f87ddc18 d3d9: give the font shadow colour a defined start value
The d3d9 HLSL and Cg font renderers start color_dark at zero, as d3d8
does. It is only read by the drop-shadow pass, under the same has_drop
test that sets it, but once the quad emitter is inlined GCC no longer
ties the read to that test and reports -Wmaybe-uninitialized at -Os.
2026-09-23 05:43:34 +02:00
LibretroAdmin 35351ca9ca ozone: name the message box's slice size for what it is
dialog-slice.png is square, so one edge is the source size on both
axes.
2026-09-23 05:31:35 +02:00
LibretroAdmin fbedb1848c companion, modeline: clear the unused-symbol warnings
ct_anim_first is built only where the decode threads that ask it
are. The modeline goldens and the EDID parse test drop the engine's
log lines through a do-nothing function rather than a macro that
swallowed its arguments, so the buffers the engine formats them into
are used, and the golden tool needs no variadic macros.
2026-09-23 05:31:32 +02:00
LibretroAdmin 9f0351f9fa ctr, gx, gx2, gxm, rsx: the overlay setters refuse an image the page does not hold
set_alpha, tex_geom and vertex_geom compare the index with the page's
count and return with no page loaded; the "&overlay[image] is NULL"
tests they had can never be true.

tools/overlay_setter_bounds.py now reads every driver with an overlay
interface, lets a stub setter with an empty body pass, and fails a
driver in gfx/drivers that defines the setters without being in its
table.
2026-09-23 05:31:16 +02:00
LibretroAdmin 993cc446ea d3d8, d3d9 (HLSL): an overlay image's vertex buffer is locked only when its quad changes
overlay_t keeps the quad last written to the image's vertex buffer
(vert_sent). The draw builds the quad as before and locks and fills
the buffer only when it differs, or when the buffer is new; a failed
lock leaves it to be written next frame, and a NULL lock is no longer
written through. D3D9 Cg draws from user memory and is unchanged.

tools/overlay_setter_bounds.py fails a D3D8 or D3D9 HLSL overlay draw
that locks its buffer before comparing the quad with vert_sent.
2026-09-23 05:24:45 +02:00
LibretroAdmin 6e88895542 modeline: a mode's size label and the requests are one word
video_modeline_t lists its mode under one packed size beside the
refresh label, the EDID standard timings hold theirs the same way,
and modeline_get and modeline_set_user_mode take the requested size
packed. The timing fields stay per axis. The SDL display servers pass
the packed size through to their apply, and the Windows, X11, KMS and
VideoCore servers fill and read it where they list and set modes.
The goldens are unchanged.
2026-09-23 05:21:22 +02:00
LibretroAdmin d695a9031b vulkan: an overlay page is drawn from one uniform and one vertex range
vulkan_render_overlay() takes one uniform range for the MVP and one
vertex range holding the page's whole vertex array, binds the vertex
buffer once, and draws each image's quad at its offset (firstVertex
4 * i). Each image keeps its own descriptor set for its texture, still
written in batches of 16.

GFX_INSTRUMENT counts the pages drawn and the buffer ranges they take
(overlay_draw, overlay_draw_alloc); the threaded_video overlay page
lane holds that to two ranges per drawn page on drivers that report
it, which the Vulkan validation leg runs.
2026-09-23 05:20:39 +02:00
libretroadmin 3c3561f8c4 glslang: clear two -Warray-bounds false positives
parseSwizzleSelector() bounds its checking loop by the decoded length
as well as by the selector's size. The selector never holds more than
that many entries, and the decoded length is visibly at most
MaxSwizzleSelectors, so the compiler can see every index stays inside
the selector and field-set arrays.

TokenizableIncludeFile's notifyActivated() and notifyDeleted() are
noinline under GCC and Clang. They are the only overrides of those
tInput hooks, so GCC speculatively inlines them into every pushInput()
and popInput() call and then bounds-checks the guarded body against
the smaller input objects those calls push. They run once per include.
2026-09-23 05:13:23 +02:00
LibretroAdmin d000d17abc crt_switch: requested and published sizes are words
The CRT consumer keeps the requested and last-applied core sizes as
one word each and passes sizes packed from crt_switch_res_core down
to the aspect switch. Two framebuffer fields nothing read are gone,
as are the aspect switch's two unused mode-size parameters. The
harness checks the size published for each switch.
2026-09-23 05:12:49 +02:00
LibretroAdmin af3a356f3e video: recording_dump_frame takes the frame size as one word
The software-filter path hands it the filter's packed output size as
it is.
2026-09-23 05:12:45 +02:00
LibretroAdmin 01459fddc0 task_image: the box downscaler takes and returns its size as one word
downscale_box takes the source size packed and hands the result back
the same way; a source too large to pack goes straight to the sinc
stage. task_image_process no longer returns the decoded size, which
it already stores in the image and no caller read.
2026-09-23 05:01:44 +02:00
LibretroAdmin 21cdb789e3 overlay: the output and image sizes are passed as one word
input_overlay_set_scale_factor and input_overlay_auto_rotate_ take the
packed output size their callers already hold, and
task_overlay_load_desc takes the base image's size packed once by the
loader.
2026-09-23 04:59:09 +02:00
LibretroAdmin 7ba4e7207f gdi: texture, atlas and scratch sizes are single words
The GDI driver's remaining size pairs join the ones already packed on
gdi_t: a loaded texture carries its pixel size in gdi_texture_t::dims,
and the font renderer's atlas mirror and tint scratch DIB each keep
their capacity in one word. Both DIBs are created from the same word
their extents are read back out of, so the surface and the size
recorded for it cannot drift apart, and the pixel-count multiplies go
through VIDEO_SCALE_AREA so a buffer size widens to size_t before the
bytes-per-pixel factor.

Either helper refuses a size past what a word holds rather than
recording a capacity smaller than the surface it describes.
2026-09-23 04:58:51 +02:00
libretroadmin e77492d102 glslang: read a double's bits through memcpy in OutputDouble
The binary dump of a double copies its bits into an unsigned 64-bit
word with memcpy rather than reading the double through a long long
pointer, which broke strict aliasing and tripped -Wstrict-aliasing at
-Os. The word is unsigned so shifting its top bit out is defined, and
the loop only shifts it, so each printed digit is the matching bit of
the value.
2026-09-23 04:56:28 +02:00
LibretroAdmin 6a6ef15435 companion: the thumbnail engine's sizes are words
companion_thumbs keys its cache, requests, animations, deliveries and
scaler on one VIDEO_SCALE_PACK size, and refuses a size with an empty
axis. The Win32, Cocoa and Qt companions carry it the same way through
their own pane and pixmap helpers. A decoded image that does not pack is
not scaled rather than scaled from a clamped size.
2026-09-23 04:52:02 +02:00
LibretroAdmin 85a8c089a5 d3d10/11/12: the overlay setters write a copy the draw uploads once
A page's sprites live in a CPU copy (overlays.shadow): set_alpha,
vertex_geom and tex_geom write their sprite there and mark the page
dirty, and the overlay draw uploads the whole page in one map when it
is - a WRITE_DISCARD on D3D10/11, so the frame before keeps the buffer
it drew from. A page load resets the copy and leaves the upload to the
first draw. The setters still refuse an index off the page.

tools/overlay_setter_bounds.py fails a D3D10/11/12 setter, or the
helper it goes through, that maps the sprite buffer.
2026-09-23 04:41:10 +02:00
LibretroAdmin 9b731bfda0 video_filter: sizes cross the softfilter wrapper as words
rarch_softfilter_new, rarch_softfilter_get_output_size and
rarch_softfilter_process take the size packed, the wrapper keeps its
maximum as one word, and video_thread_filter hands the size back the
same way. The plugin interface in softfilter.h keeps its two axes.
2026-09-23 04:40:17 +02:00
libretroadmin 94e754ff94 ozone, companion: clear -Wmaybe-uninitialized and -Wformat-truncation
ozone_alloc_node() zeroes the node's packed attr word in one store
instead of going through the height, sublabel and wrap setters, each of
which reads the word first. Bits outside those three fields are now
defined too, so the bitwise node copy in ozone_snapshot_visible() never
carries indeterminate data.

companion_core_browse_name() bounds the volume label with a precision
in the drive-name format, so the letter suffix always fits in the
64-byte buffer. Real volume labels are far shorter than that bound.
2026-09-23 04:34:54 +02:00
LibretroAdmin 65175892f8 video: the scaled-viewport helpers take the viewport size as one word
video_viewport_get_scaled_aspect2 and video_viewport_get_scaled_integer
take the packed size; the frontend hands them vp->full_dims as it is,
and gx passes its own full_dims.
2026-09-23 04:34:27 +02:00
LibretroAdmin 3733267c5f settings: the last two pairs in settings_t are words
The desktop companion's saved window geometry and the camera's
requested capture size were the two width/height pairs left in
settings_t. Six unsigned fields, three words.

The companion's geometry is a rectangle four UI drivers agree on --
Qt, win32, Cocoa and the two companion test harnesses -- written as
four axes at a time and read back as four. It is an origin and a size
now. Its origin packs as a signed word even though nothing negative
reaches it: both writers clamp at zero and every row is bound to
32767, so VIDEO_POS_PACK's range covers it with the sign intact
rather than laundered.

The four config keys are unchanged, which matters more here than
usual: tools/companion_qt_persist_test.sh reads
desktop_menu_window_width and _height straight out of the retroarch.cfg
RetroArch writes on quit, and companion_win32_test / companion_cocoa_test
assert on the saved values. The four rows move from S_UINT_EX, which
binds by field name, to S_UINT_AT_EX with a half tag, so they are
literal in configuration.c now; S_UINT_AT_EX is added to the one label
pass in msg_hash_us.h that did not define it.

The camera pair needed none of that. No config key and no menu row
binds either axis -- it is read once, in retroarch.c, to ask the camera
driver for a size.
2026-09-23 04:34:14 +02:00
LibretroAdmin 647e0a32fb Threaded video: an overlay alpha apply sets only the images that changed
The video thread keeps the float bits it last handed the driver for
each overlay image (alpha_applied, its own) and an apply passes on
only those that differ, so one press on a page is one driver set. A
page load marks them all unknown and the next apply sets every image;
without the array every image is set at every apply.

The overlay page lane checks one change is one set, a repeated value
is none, and a new page gets all of its images.
2026-09-23 04:33:54 +02:00
LibretroAdmin 26d185d03c overlay: an image's alpha is set only when it changes
input_overlay_alpha_pass() works out every image's alpha for the poll
- opacity, a pressed desc's highlight, 0 for an LED image that is off -
into scratch and hands the driver only the images whose alpha differs
from the last one it was given. A page load, through load() or
load_textures(), forgets what was sent, so the next pass sets them
all. The per-image sent and scratch arrays are one block sized for the
pack's largest page; without it every image is set every pass.

The LED hiding moves with it into input/input_overlay_alpha.c, and the
immediate hide of an LED going out goes through the same record, so a
light that goes out and back on between two polls is shown again.
overlay_visibility_bounds_test pins no-change, press/release, forget,
opacity change, that LED case and the no-block fallback.
2026-09-23 04:16:59 +02:00
LibretroAdmin 7c7fbeb37a threaded_video test: the aspect index is read from the viewport flag word
The viewport publish carries the aspect index in six bits of slot 0,
so the lane decodes it from there.
2026-09-23 04:16:20 +02:00
LibretroAdmin 5fc62fef3f video: the statistics text has room for every line and stays in bounds
The statistics overlay's text and the threaded wrapper's copy of it are
both VIDEO_STAT_TEXT_SIZE (1664), room for every section at once. Each
line goes through video_driver_stat_appendf(), which keeps the text
terminated within the buffer and takes nothing once it is full.

The threaded video harness's stat text bounds lane appends past the
buffer and checks the length, the terminator and the byte after it.
2026-09-23 04:16:16 +02:00
LibretroAdmin 2e4fc9fcaa settings: the windowed-mode geometry is two words
The window's own size and the ceiling its auto-resize honours were
four unsigned fields in settings_t. Both pairs are one word now, using
the half-aware binding the custom viewport got: the config keeps
video_windowed_position_width/_height and video_window_auto_width_max/
_height_max as four keys and the menu keeps four rows, each carrying
which half it is.

The four menu rows move from S_UINT, which takes a field name, to
S_UINT_AT_EX, which takes an offset -- that is where the half tag
rides. Two of the blocks that include this header define only the
macros their rows used, so S_UINT_AT_EX is added to the enum pass in
msg_hash.h and the label pass in msg_hash_us.h. The auto-resize pair
was config-bound through the S_UINT pass, which binds by field name;
with the field gone those two rows are literal in configuration.c like
the size pair beside them.

win32_common is the reason this pair was worth packing. It copied all
four axes out of the window, then subtracted the border from the width
and the border, title bar and menu bar from the height -- four writes
to a stored pair, one axis at a time, with the struct holding a
half-adjusted size in between. The chrome comes off two locals now and
the pair is stored once.

Not touched: window_position_x/y. They launder a negative coordinate
through an unsigned field -- SDL and win32 both hand over a signed
position and cast it in, and sdl3_common casts it back out -- so
packing them is a question about what the config file should hold for
a window on a monitor left of the primary, not a mechanical change.
2026-09-23 04:15:32 +02:00
libretroadmin cb83fb2d37 Qt: radio-button choices are enumerated in unsigned integers
UIntRadioButtons walks a setting's range with unsigned counters
instead of a float, which stops advancing past 2^24. The bounds go
through qt_setting_bound(), negative bounds clamp to zero, a step
below one is one, and the walk stops before it would pass the maximum
or wrap. At most QT_RADIO_BUTTONS_MAX buttons are made, so a row with
no enforced maximum lays out a bounded list instead of hanging.
2026-09-23 04:11:31 +02:00
LibretroAdmin 2c8d6b4555 settings: the custom viewport is two words in settings_t
video_viewport_settings_t held x, y, width and height as four members
while video_viewport_t next to it held the same rectangle as two. It
was the one pair in the tree the campaign had been leaving alone, with
a comment on the struct saying why: the menu and the config bind a
setting by address, and a packed half has no address to bind.

So the binding learns about halves. A config row carries CFG_HALF_HI
or CFG_HALF_LO and its ptr addresses the whole word; every read and
write of a uint or int row goes through cfg_uint_get/set and the
signed pair rather than dereferencing ptr. A menu row carries the same
in SD_FREE_FLAG_PACKED_HI / _LO, seeded from two spare bits in the
descriptor's value_offset -- settings_t is nowhere near 2^30 bytes --
and setting_uint_get/set and the signed pair stand in front of
value.target for the generic paths: the typed-value setter, the
default left and right handlers, and setting_reset_setting. Rows
without a tag read and write exactly as before.

The config file is unchanged: custom_viewport_x, _y, _width and
_height are still four keys, so a config written before this still
loads.

x and y went through the uint rows before, with a cast, because a
whole-word store round-trips a negative fine. A half store does not --
the unsigned accessor clamps, so -100 would have come back 0 -- so
they move to the int rows, where the half accessors sign-extend
through VIDEO_POS_X/Y.

Most of the consumers get shorter: video_driver.c and rgui.c were
packing these four into a video_viewport_t's two words and unpacking
them back, and the publish of the seqlock'd parameters was doing the
same. Those are word copies now.

Checked by exhausting both accessor pairs over the ranges the setting
rows allow -- 50015 size cases and 53865 origin cases, including
negative origins -- confirming each axis round-trips and that writing
one never disturbs its partner.
2026-09-23 04:06:37 +02:00
libretroadmin 7d6c71b491 Qt: spin box ranges are clamped in double, and a resync is shown once
A setting's range is float, and a spin box with no enforced maximum
falls back to INT_MAX; that ternary is a float, and 2147483648.0f
converted back to int is undefined - at run time on x86 it is
INT_MIN, which collapses the box to a single value of -2147483648.
The resync in paintEvent then never matches the stored value and
re-sets it on every paint, repainting without end (Auto-Shader Delay,
SaveRAM Autosave Interval).

UIntSpinBox, IntSpinBox and SizeSpinBox now take their bounds through
qt_setting_bound(), which clamps in double and gives an unenforced or
NaN bound the widget's default. Their paint-time resync compares
against the stored value clamped to the box's range, so a value the
box cannot hold is shown at the nearest bound once.
2026-09-23 03:26:02 +02:00
LibretroAdmin c2aa9282fe video: the portrait bias is published only where it is read
Off mobile the two portrait-bias slots were a copy of the two
landscape ones: published every time the parameters changed, loaded by
every read, and never looked at. Both sites that reach for the portrait
pair pick it on the window being taller than it is wide, and both sit
inside #if defined(RARCH_MOBILE).

They are not published off mobile now, and the pair sits last so every
other slot keeps its index on both platforms. Eight slots on desktop,
ten on mobile.

The two fields of the snapshot struct are guarded the same way. Off
mobile nothing assigns them, so a reader added outside the #if has to
fail the build rather than pick up whatever the stack held -- which is
what it would have done had the fields stayed.

On packing the bias pairs into one slot each, which is the other way to
spend these four slots: measured rather than assumed, and it does not
work. The consumers are (int)(padding * bias) and a roundf of the same
shape, so the only question is whether a sixteen-bit bias ever moves
the pixel they land on. Over the two-decimal values the menu and its
typed entry can produce, against paddings from 1 to 8192, it moves
15382 of 827392 -- including at bias 0.53 -- and 1.6% of six-decimal
values, the precision a config file keeps. A pixel of viewport offset
is not worth two slots.
2026-09-23 03:16:46 +02:00
LibretroAdmin e12ccc1df2 hub75: the driver's five stored sizes are five words
hub75_t held the panel chain's own size, the core frame's, the menu
frame's, and where the last frame landed on the panel as an origin and
a size; hub75_overlay_t held an overlay image's size. All five pairs
are one word each now.

The panel width is the row stride, so it is read three times per pixel
in hub75_refresh_once and once per pixel in the overlay blend. Both
take it into a local once rather than through the macro each time.

Left as they are: the overlay's tex_w/tex_h and vert_w/vert_h, which
are normalised 0..1 coordinates rather than sizes, and three blocks of
function locals that never leave the frame they are computed in.
2026-09-23 03:16:43 +02:00
LibretroAdmin cbcc8696ec overlay: LED images are the pack's to name; ledN_map reaches nul buttons only
A pack names the image that shows an LED with overlayN_descM_led = K
(1-based, as ledN_map counts): under led_driver = "overlay" that image
shows while the core has LED K lit, and a pack that names any LED image
is not mapped at all. For a pack that names none, ledN_map still picks
slots of the page, but only the image of a "nul" button answers to it:
a desc that does something when pressed is never an LED, so a config
made for an LED overlay leaves a gamepad pack's controls where they are.

The LED driver reports the core's LEDs (input_overlay_set_led) and
input/input_overlay_leds.c decides what they hide. The per-slot
visibility array, its enum and the parameter threaded through the
alpha and poll paths are gone.

overlay_visibility_bounds_test now builds input_overlay_leds.c with
the LED driver and pins the map/named/off cases and the page bound;
overlay_led_hidden_test runs a mapped pack and a named pack under Xvfb
with Up held.
2026-09-23 03:16:29 +02:00
LibretroAdmin 808f35f950 d3d8: the overlay setters refuse an image the page does not hold
set_alpha, tex_geom and vertex_geom compare the index with the page's
count and return with no page loaded. load() refuses a failed calloc
and frees a page whose texture could not be made, so no size is left
without an array behind it; enable() sets its flag once rather than
inside a loop that never runs for an empty page, and full_screen()
takes a NULL page. tools/overlay_setter_bounds.py now reads d3d8.c.
2026-09-23 03:16:25 +02:00
LibretroAdmin d35d6c2feb runloop: read output dims once for overlay and FULL aspect checks
runloop_check_state() read video_driver_get_output_dims() separately
in the overlay block and the ASPECT_RATIO_FULL block. Read it once
into the existing output_dims snapshot (now declared unconditionally)
and use it in both.

Resolve the TODO/FIXME above the FULL aspect block by documenting why
the rest of it stays as is: each block keeps its own last_dims since
they are gated independently, and the check is polled from the run
loop because video_driver_set_output_dims() runs on the video thread
under threaded video, where the blocking driver pokes done by
video_driver_set_aspect_ratio() cannot be issued.

No functional change.
2026-09-23 03:08:14 +02:00
LibretroAdmin c1da28604d video: the viewport-parameter publish is ten slots, not fifteen
The seqlock'd viewport parameters were fifteen atomic ints, walked in
full by the publish (a compare against each, then a store to each) and
by every read. The video thread reads them once per frame through
video_driver_update_viewport, and every shader bind reads them again
through video_driver_get_rotation_snapshot.

Ten now. The custom viewport was four slots holding one rectangle:
its own setting rows bound the origin to -9999..9999 per axis and the
size to 1..9999, so VIDEO_POS_PACK and VIDEO_SCALE_PACK cannot reach a
clamp, and it is two. Four bounded selectors -- scale_integer,
aspect_ratio_idx, video_scale_integer_scaling and
video_scale_integer_axis -- shared thirteen bits of one slot, with a
negative-array-size check that fails the build if any of those enums
grows past the field it goes into.

rotation and core_req_rotation stay a slot each and are deliberately
not narrowed. Both trace back to a core's RETRO_ENVIRONMENT_SET_ROTATION,
which a core may set to any unsigned, and shader_glsl and shader_gl_cg
pass the value to a shader uniform without reducing it -- so a field
too small, or a modulo four on the way in, would change what a shader
sees for a core that sets something unexpected.

The four bias floats keep a slot each. Two of them are a mirror of the
other two off mobile, which is real waste, but folding a pair into one
slot means sixteen bits an axis, and that is a quantisation rather than
a repacking; left alone here.

Checked by exhausting the flag word over all 1456 legal combinations of
the four selectors and the custom viewport over its range, both
round-tripping exactly, and by confirming the sizing check fails the
build when a field is made too narrow.
2026-09-23 03:07:00 +02:00
LibretroAdmin f6cf4e07a6 materialui: the on-screen keyboard's text box is two words
osk_textbox_x, _y, _w and _h are one rectangle held as four ints. The
whole of it is written in one place, when the text box is drawn, and
read in one place, by the pointer hit test that decides whether a tap
landed inside it. Two fields now: an origin in VIDEO_POS_PACK's layout,
because the box's x goes negative when a wide message pushes it off the
left edge, and a size in VIDEO_SCALE_PACK's.
2026-09-23 02:59:30 +02:00
LibretroAdmin 25d078ca08 overlay: a hidden image stays hidden when pressed; alpha bytes saturate
The pressed-desc highlight in input_overlay_post_poll() asks the page's
visibility first: an image the LED driver has hidden (led_driver =
"overlay", ledN_map) stays hidden while its button is held, instead of
showing for exactly as long as it is pressed.

An overlay alpha is opacity times a desc's alpha_mod, which packs set
above 1 for pressed buttons. Drivers that pack it into a byte (d3d8,
d3d9 cg/hlsl, d3d10/11/12, gdi, gx, gx2, gxm, sdl2) now convert through
VIDEO_ALPHA_BYTE(), which saturates, so a pressed button no longer
wraps to a dimmer alpha than a released one. tools/overlay_setter_bounds.py
fails any driver that packs one without it; overlay_led_hidden_test holds
Up through XTEST under Xvfb and checks the hidden image stays off screen.
2026-09-23 02:52:31 +02:00
LibretroAdmin 24a012f7a8 video: the statistics overlay's text is built out of the frame body
The statistics text is built in video_driver_frame_statistics(), a
VIDEO_NOINLINE helper called only while the overlay is shown, so
video_driver_frame carries none of its formatting.
2026-09-23 02:50:06 +02:00
libretroadmin e4ed43c4d2 Reapply "video: the overlay viewport's seqlock gets its release fence"
This reverts commit 02d267c75c.
2026-09-23 02:24:51 +02:00
github-actions af46d2beed Fetch translations from Crowdin 2026-09-23 00:24:43 +00:00
libretroadmin 02d267c75c Revert "video: the overlay viewport's seqlock gets its release fence"
This reverts commit 025f456b2a.
2026-09-23 02:09:38 +02:00
sonninnos 434faf2aad Move Display Information to Information root + add menu icon 2026-09-23 03:01:20 +03:00
libretroadmin 119e832217 scanline sync: read the output lines outside the frame path
The Scanline Sync helpers are built on every platform. When the sync is on, video_driver_init_internal() and a CRT mode switch read the output lines directly, so the first frame after startup or a mode change no longer spends time on the lookup. Only switching the sync on at runtime leaves the read to its first frame, and nothing is read while it is off.
2026-09-23 01:51:39 +02:00
libretroadmin ac52e5206d scanline sync: read the output lines only when the sync runs
video_driver_scanline_before_frame() reads the display's active and total lines itself, on its first frame and again after a video driver reinit or a CRT mode switch clears them, so nothing is fetched while Scanline Sync is off and each change is read once. An output size that cannot be read is retried about once a second.

The lookup is file-local to video_driver.c and built only with HAVE_D3DKMT, the one configuration with a beam position, and the display-change handler and d3dkmt_init() no longer touch it.
2026-09-23 01:35:06 +02:00
sonninnos 14e4374ecc Add more missing sublabels 2026-09-23 02:16:31 +03:00
LibretroAdmin 0778788524 menu: an overlay with nothing to press leaves the mouse to the menu
The menu turned its mouse off whenever any overlay was alive, so that
a press on the overlay is not also a click in the menu. An LED overlay
(led_driver = "overlay") is a page of "nul" buttons whose images are
lit and unlit - nothing on it responds to a press - yet it took the
menu's cursor away all the same: fullscreen with such an overlay shown
in the menu, no cursor was drawn and the mouse did nothing.

The desc loader now marks a page OVERLAY_TAKES_INPUT when any of its
descs acts on a press: every type but a button, and a button that
binds something other than "nul". The menu gives its pointer up only
to an alive overlay whose active page carries that flag, or whose own
pointer mode (input_overlay_pointer_enable) is on. struct overlay's
flags widen to 16 bits for the ninth flag.

overlay_menu_mouse_test runs the real binary under Xvfb in the overlay
on-screen workflow: with a "nul" pack the RGUI cursor must follow the
X pointer, with the same pack bound to "a" it must not, each with
threaded video off and on. It fails on the old code (no cursor with
the "nul" pack) and passes on the new.
2026-09-23 01:12:38 +02:00
sonninnos 8d4bccac55 Remove outdated wasapi+sleep+midi-hack 2026-09-23 01:51:25 +03:00
libretroadmin d9b13c0640 config.def.h: make the menu enums visible without HAVE_MENU
configuration.c expands the single-source setting rows from
settings/settings_def_menu_filebrowser.h in every build, so the
default of menu_file_browser_extension_display names
MENU_FILE_BROWSER_EXTENSION_DISPLAY_ALWAYS even when the menu is not
compiled in. Include menu/menu_defines.h ahead of the HAVE_MENU gate;
it only carries enums and retroarch_types.h already pulls it into
menu-less builds, so builds with the menu see no change.
2026-09-23 00:39:20 +02:00
sonninnos 9e3ee45f9e Scanline Sync refactor (#19610) 2026-09-23 00:37:45 +02:00
RyunamandLibretroAdmin 1243747c4b Config: Convert Ozone 'Color Theme' setting to array (#19451)
* Config: Convert Ozone 'Color Theme' setting to array

* Ozone: keep the color theme list in one place

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

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

While here:

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

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

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

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

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

---------

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

Also move the Always / Duplicates Only / Never strings in
msg_hash_us.h from after the multimedia group to just ahead of it,
beside the file browser group, and regenerate
tools/settings_table.reference for the new setting.
2026-09-22 23:53:33 +02:00
LibretroAdmin 03478265b7 menu: tidy up file extension display modes
Follow-ups to the File Extension Display setting (#19611):

- Move the helpers out of menu_cbs_get_value.c, where they were not
  value callbacks, and make them static next to their only callers:
  the prepare step in menu_displaylist.c and the label formatter in
  menu_driver.c.  Only menu_file_browser_stem_length() stays shared,
  declared in menu_entries.h; menu_cbs.h goes back to how it was.
- Replace the bare 0/1/2 entry states with
  enum menu_file_browser_extension_state.
- Duplicate detection worked out every name's length-without-
  extension again inside the qsort comparator, each time going
  through path_basename() and its archive-delimiter scan.  Work it
  out once per entry and sort an array of {cbs, path, length}
  instead, which matters in large ROM folders on slow hardware.
- Compare names with TOLOWER instead of strncasecmp, which nothing
  else in the frontend relies on.
- Build the " (ext)" hint with offset-tracked strlcpy calls instead
  of chained strlcat, which rescanned the buffer on every append.
- Put the rich-label condition in menu_entry_get() back to plain
  cbs->action_label; every entry gets a label callback, so the extra
  file_extension_state test never changed anything.

No change in what gets drawn.
2026-09-22 23:53:29 +02:00
Chris Bradel 8b6b60d37a menu: add file extension display modes (#19611)
Add a File Browser setting with Always, Duplicates Only, and Never modes. Duplicate detection uses the filtered view and preserves list order. Format only the drawn label so stored paths and menu entry paths remain unchanged.
2026-09-22 23:45:29 +02:00
LibretroAdmin af6aa7a1bc gdi: the driver's nine stored sizes are nine words
gdi_t carried eight width/height pairs and struct gdi_overlay a ninth:
the core frame's size, the screen's, the window surface's, the DDB's,
the menu frame's, the menu compositing surface's, the capacities of the
two cached scratch DIBs, and an overlay's texture size.

Two of them are there specifically to be compared against a size and
acted on. bmp_dims is the clearest, and its own comment says why it
exists: it tracks the DDB's real size because comparing against the
core's frame size instead triggers a destructive DeleteObject +
CreateCompatibleBitmap every frame, racing WM_PAINT and producing
visible flicker. That comparison is now one word.

The scratch DIB slots said they kept width and height apart rather
than a pixel count "because BITMAPINFOHEADER cares about both". It
still does; both axes are still there, in one field.

Two "use this size, or fall back to that one" reads in the paint path
picked each axis independently. Both fields in each pair are only ever
written as a whole, so the fallback is one ternary over two words.
The overlay's zero-size guard is left per-axis: it exists to reject a
malformed overlay, and a word test would accept one whose width alone
came back zero.

Also corrects three comments still naming vp.full_width/full_height
and bmp_width/bmp_height, which went to single words earlier.
2026-09-22 23:06:19 +02:00
LibretroAdmin 22bb3ddedc gl2: the driver's four stored sizes are four words
gl2_t held the output size, the streaming texture's size, the viewport
the last frame went out at, and the size of the retained backbuffer
copy as eight fields. All four are one word each now, which finishes
the GL family -- gl1, gl3 and the FBO chain have already been through.

tex_w and tex_h were the pair with reach: thirty-five sites, most of
them sizing a buffer from the product of the two. Those go through
VIDEO_SCALE_AREA, so the byte counts widen before the bytes-per-pixel
multiply rather than after.

The retained backbuffer's pair was compared a half at a time to decide
whether the texture could be reused, then stored in two statements;
it is one word either way. Five sites clamped the readback rectangle
to the output size with (a > b) ? b : a written out per axis; they use
MIN.
2026-09-22 23:01:04 +02:00
LibretroAdmin d04d2a6adc video_defines: a packed size's area, widened to size_t
VIDEO_SCALE_W(d) * VIDEO_SCALE_H(d) had grown to forty sites across
twenty-three files, nearly all of them sizing an allocation. Both axes
clamp at 65535, so the product alone still fits in a 32-bit unsigned --
0xfffe0001 -- but the thing being computed is almost never the pixel
count on its own. It is the pixel count times the bytes per pixel, and
that multiply wraps. A wrapped allocation size does not fail; it
succeeds at the wrong size and the write past it comes later.

VIDEO_SCALE_AREA(d) widens to size_t first, so the bytes-per-pixel
multiply that follows is done at pointer width on any 64-bit target.
Three call sites had already been written with a (size_t) cast in front
of the first half for exactly this reason; the rest had not.
2026-09-22 23:01:01 +02:00
LibretroAdmin 067df73763 gl3: the driver's four stored sizes are four words
gl3_t kept the output size, the viewport the last frame went out at,
the hardware render target's maximum size and the menu texture's size
as eight fields. All four are now one word each.

The menu texture's pair was the one that could drift: its guard
compared each half against the incoming size to decide whether to
reallocate, then stored the two halves separately. It is one word
either way now.

Four sites clamped the viewport to the output size with
(a > b) ? b : a written out per axis, twice over; they use MIN.
2026-09-22 22:55:40 +02:00
LibretroAdmin f89bae0c26 video: frame conversion and the recording readback take size words
video_driver_convert_xrgb2101010, the threaded wrapper's frame
conversion and video_thread_record_take take the frame size as one
packed word. The recording readback keeps its encoder size and its
last source size as words, so a viewport change is a word compare and
the recorder's gpu_dims reaches it as it is stored.
2026-09-22 22:55:37 +02:00
LibretroAdmin 1c6a1c1ea5 gl3: the non-Slang render chain drops four fields nothing reads
last_width[GL_CORE_NUM_TEXTURES], last_height[GL_CORE_NUM_TEXTURES],
hw_render_last_width and hw_render_last_height are declared in the
chain struct and never written or read, here or anywhere else in the
tree.
2026-09-22 22:55:31 +02:00
LibretroAdmin 5514fd3eef gfx_surface: surfaces are made from a size word
gfx_surface_new and gfx_surface_new_static take the surface's size as
one packed word, stored as given, and the thumbnail's surface helper
passes it through. VIDEO_SCALE_FITS says whether a size packs
unclamped; the thumbnail and overlay loaders check it before sizing a
surface for a decoded image, since a clamped axis would allocate less
than the decoder writes.
2026-09-22 22:48:43 +02:00
LibretroAdmin 84fbb08706 gl1: the driver's four stored sizes are four words
gl1_t kept the last frame's size, the screen's, the menu frame's and
the viewport the last frame went out at as eight fields. All four pairs
are now one word each.

Three of the four existed to be compared against an incoming size and
then written back, a half at a time, to decide whether to reallocate:
the frame path in gl1_gfx_frame, the one in the render-target setup,
and the menu path guarding MENU_SIZE_CHANGED. Each is now one word
compared and one word stored.

The font viewport's fallback reads better for it as well. It wanted the
screen size, or the frame size when the context driver has not reported
one yet, which took a VIDEO_SCALE_PACK over two ternaries picking each
axis on its own; it is one ternary over two words.

Left alone: inv_win_width/inv_win_height, which are reciprocals for the
vertex math rather than a size.
2026-09-22 22:43:53 +02:00
LibretroAdmin 6d16b0e0f6 vulkan: the driver's six stored sizes are six words
vk_t held six width/height pairs: the output size, the streaming
texture's size, the viewport the last frame went out at, the size of
the retained backbuffer copy, the extent the last readback actually
copied, and the size the hardware-render interface last saw.

Three of them were being compared a half at a time to decide whether to
rebuild something. The retained backbuffer is the clearest: two
separate comparisons against the swapchain's width and height decided
whether the copy could be reused, and the pair was written back in two
statements after the reallocation. That is now one word compared and
one word stored, so a reallocation cannot leave the struct claiming a
size the image does not have.

Two viewport clamps read (a > b) ? b : a spelled out per axis over
three lines each; they use MIN, which is what they were.
2026-09-22 22:43:49 +02:00
LibretroAdmin 0298cc3173 video: drop read_frame_raw, which no driver provides
No video driver fills read_frame_raw - GL2's implementation only built
under a test define that nothing sets - so the slot, its frontend
wrapper, GL2's implementation and the screenshot path that consumed it
are gone. The screenshot task always takes the raw frame from the
frame cache, and prefers a viewport read on any hardware-context core.
2026-09-22 22:42:43 +02:00
LibretroAdmin 7c6966487b translation: the frame size travels as one word
The translation driver's translate hook and its HTTP and Apple
implementations take the frame size as one packed word, which
run_translation_service and the response handler keep as they get it
from the frame cache. The software-convert context's size fields,
which the callback no longer reads, are gone.
2026-09-22 22:38:33 +02:00
LibretroAdmin fddea56df2 shader_gl3: the slang filter chain's stored sizes are words
The GL3 half of the same change: gl3_framebuffer's size and the size a
gl3_pass last rendered at are one word each, and gl3_pass_get_output_size
returns the size rather than writing it through two out-pointers.

Its float-to-pixel conversion goes through VIDEO_PX for the same reason
as the Vulkan one -- a plain cast of roundf() is undefined outside int
range.

Two blocks here declared a pair of locals only to copy the input
texture's size into them and hand them straight to
gl3_framebuffer_set_size, with the size comparison above spelled out a
half at a time. Both are now one packed comparison and a direct call.
Six declarations across the file were indented a level too deep, which
is how those blocks looked from a distance; they are gone with the
locals and fields they belonged to.
2026-09-22 22:38:17 +02:00
LibretroAdmin 1f9f2275bd shader_vulkan: the slang filter chain's four stored sizes are words
slang_framebuffer, slang_pass and vulkan_filter_chain between them held
four width/height pairs as eight fields -- a target's size, the size a
pass last rendered at, the chain's maximum input size, and the source
size accumulated across a per-frame build. All four are now single
words.

Two of them were being passed through out-pointers, which is what made
the packing worth doing rather than merely tidy. slang_pass_set_pass_info
wrote its result into &source_w and &source_h for the caller to feed
into the next pass, and slang_pass_get_output_size did the same one
level down; at the deferred call site those out-pointers were aimed
straight at the chain's own deferred_source fields. Both now return the
size as a word, so the chain walk carries one value per pass instead of
a pair the callee reaches back through two pointers to set.

slang_pass_get_output_size also converted its two floats with a plain
cast of roundf(), which is undefined for anything outside int range.
It goes through VIDEO_PX, which rounds and bounds.

Four declarations in those structs were indented one level too deep;
they are gone with the fields they belonged to.
2026-09-22 22:38:14 +02:00
LibretroAdmin c8f10ce378 screenshot: the task carries its frame and output sizes as words
The screenshot task keeps the frame's size and the size it writes out
as two packed words; screenshot_dump, the rotate step and the PNG/BMP
wrappers take them as words and unpack only where libretro-common's
encoders and scaler want width and height. The output buffer is
allocated straight into the task state, and the scaler pointer sits in
the PNG branch that uses it.
2026-09-22 22:35:52 +02:00
LibretroAdmin c6212745a6 video: the cached frame's API speaks its size word
video_driver_cached_frame_info, the cached_frame_read callback and
video_driver_cached_frame_publish pass the frame's size as the packed
word the cache already stores. The reinit snapshot keeps it as one
word from read to republish, and the screenshot copy carries it the
same way.
2026-09-22 22:32:17 +02:00
LibretroAdmin fb5dc31ecb gl2: the Mali scissor workaround packs its pair, and compiles as C89
gfx_display_gl2_discard_draw_rectangle took width and height apart and
kept the last pair it saw in two statics, comparing them a half at a
time to decide whether the scissor rectangle needed rebuilding. It now
takes the size word its only caller already holds and keeps one static,
so the test is a word comparison. That also retires the two locals
gfx_display_gl2_draw was unpacking solely to pass them back in.

The same block had a declaration after a statement, which C89 does not
allow. It has been there a long time: the whole workaround sits behind
#ifdef MALI_BUG, which is defined only on __arm__ and __aarch64__, so
no lane in the compile matrix -- all of which run on x86-64 -- has ever
preprocessed it. Compiling gl2.c with __arm__ forced is what found it.
2026-09-22 22:30:16 +02:00
LibretroAdmin d5621fd9e7 gl2/gl3: the FBO chain's three sizes are three words
struct video_fbo_rect held six unsigned fields -- img_width/height,
max_img_width/height and width/height -- three pairs describing one
pass of the shader chain: what it renders this frame, the largest it
renders at any input size, and the texture that holds it. They are now
img_dims, max_img_dims and dims.

The struct goes from 24 bytes to 12, and each driver keeps
GFX_MAX_SHADERS of them, so gl2 and gl3 each drop 768 bytes of state.

gl2_renderchain_recompute_pass_sizes and its gl3 twin are where this
matters. A shader pass scales its two axes independently -- absolute on
one, relative to the input on the other -- so the old code wrote each
axis of each pair in a switch of its own, clamped the four halves to
GL_MAX_TEXTURE_SIZE one at a time, then carried four loop variables to
the next pass. Six of those eight writes could land without their
partner. The axes are still found separately, because they genuinely
are separate, but into plain locals; the pair is joined only where it
is stored, and the loop now carries two words instead of four.

Verified over the 156 compile-matrix lanes, which include six for these
two drivers (desktop, GLES2, GLES3 and the C89 and no-threads gates).
2026-09-22 22:30:13 +02:00
LibretroAdmin 4275acf3fc video: an output's size is one word
video_output_info_t carries a connected output's size as one packed
word, which the display servers fill in one store and the menu and CRT
switch lists read their labels from.
2026-09-22 22:25:26 +02:00
libretroadmin 7c4d623e50 samples/dirwalk: fast-path and sort-only lanes hold the window open
The fast-path and sort-only deferral lanes pin the mechanism - a walk
that fits the window completes inline with no task and no refresh, and
a completed walk too large to sort inline hands only the sort to the
task. They now run with the instrument frozen, so the outcome no longer
depends on whether the small tree walks inside 4ms of wall-clock, which
a TSan build on a CI runner does not. The pacing lane keeps the real
clock and owns the wall-clock claim.
2026-09-22 22:25:00 +02:00
LibretroAdmin 2c1329fd33 video: a display mode's size is one word
video_display_config_t carries a mode's size as one packed word. The
display servers fill and match their resolution lists on it and keep
the current mode's size as a word; the SDL servers sort the list on the
word, which orders by width and then height as before.
2026-09-22 22:23:04 +02:00
LibretroAdmin a19855295a video: the display server's set_resolution takes the size word
set_resolution on the display server, its frontend wrapper and every
display server's implementation take the requested size as one packed
word; a zero axis still means the current one, filled in on the word.
The win32 server keeps the mode it restores as one word, and the menu's
resolution picker parses into one before writing the fullscreen
settings.
2026-09-22 22:18:22 +02:00
LibretroAdmin f65f4cfe5d menu: materialui's savestate list drops its unreachable ticker branch
materialui_render_menu_entry_savestate_list is a copy of
materialui_render_menu_entry_default with the smooth ticker switched
off -- if (0 && mui->flags & MUI_FLAG_USE_SMOOTH_TICKER) -- so only
the plain ticker has ever run there. The dead branch was also the only
writer of value_x_offset, which was initialised to zero and added to
the value's x position on every frame.

The default renderer is untouched: its ticker branch is live and stays
exactly as it is.
2026-09-22 22:15:48 +02:00
LibretroAdmin 6d9d982be3 menu: xmb's thumbnail drop shadow was already switched off
The shadow config sat behind if (0 && shadows_enable) with an else that
set GFX_THUMBNAIL_SHADOW_NONE, so the branch has only ever taken the
else. The comment above it said why -- the thumbnails carry their own
background border -- and that reason is now the whole of it.
2026-09-22 22:15:45 +02:00
LibretroAdmin e661d8daee menu: rgui's mini thumbnail loses two draw paths that never ran
rgui_render_mini_thumbnail carried a drop shadow behind if (0 &&
shadow_enable) and, in the savestate placeholder branch, an "N/A"
label behind an unconditional return. Neither has drawn a pixel.

The label's TODO said what it was waiting on -- a caption belongs on
a missing state file, not on a missing image file -- so that survives
as a comment on the background it would have sat on. With the label
gone the text position, the message lookup and the function's fb_width
unpack have no remaining reader.
2026-09-22 22:15:41 +02:00
LibretroAdmin 7ce68d42f3 gfx_widgets: context_reset and init take the size word
gfx_widgets_init, the widgets' context_reset and the volume and
load-content implementations of it take the output size as one packed
word, which becomes last_video_dims as given. The screenshot widget
keeps its box and thumbnail sizes as words, and the thumbnail scale
factor is computed from the destination and image words.
2026-09-22 22:13:30 +02:00
LibretroAdmin 37f5f0b19d menu: rgui's thumbnails carry their two sizes as words
thumbnail_t held four unsigned fields: max_width, max_height, width and
height. Both are pairs that have to agree -- rgui_process_thumbnail
sets width and height together from the decoded image and the copy loop
below walks exactly that rectangle, so a field updated without its
partner reads off the end of the buffer. They are now one word each,
max_dims and dims, which makes that impossible to write.

One of the two was already packed at its source: mini_thumbnail_max_dims
is a packed word that was being taken apart into max_width and
max_height for two thumbnails and read back as halves. Those four
assignments are now two word copies.

rgui_downscale_thumbnail's max_width/max_height parameter follows, since
its only caller now holds the word.

The halves are still what most of the arithmetic wants, so the two
functions that read them repeatedly unpack once into locals rather than
reaching through the macro at each use.

This costs about 270 bytes of text on x86-64 -O3, from the clamp
VIDEO_SCALE_PACK applies at the two sites that build a word from
unrelated values. That clamp is the point: it is what stops one axis
writing over the other.
2026-09-22 22:10:57 +02:00
LibretroAdmin 5a98e84e4e menu: rgui's render tree carries one framebuffer size word
Eleven functions in rgui.c took the framebuffer size as a separate
fb_width and fb_height, from the two primitives that write pixels
(rgui_fill_rect, rgui_color_rect) up through the render phases that
call them. Every one of those calls started from p_disp->framebuf_dims,
which is already a packed word, so the pair was unpacked at the top of
rgui_render and carried down seventy call sites only to be compared
and multiplied as two halves again.

The parameter is now a single fb_dims. Functions that need the halves
unpack them locally, which costs a shift and a mask once per call
rather than a second argument at every level, and the call sites pass
p_disp->framebuf_dims straight through.

Two consequences worth naming. rgui_render no longer unpacks fb_height
at all, and the framebuffer-resize test it feeds -- last_dims against
a freshly packed pair -- becomes a comparison of the two words it was
always really comparing.
2026-09-22 22:10:53 +02:00
LibretroAdmin 189a7d2669 gfx_display: texture loaders report one size word, the cursor reads it
gfx_display_reset_textures_list and its buffer variant report the
loaded image's size as one packed word, which the AI service overlay
and the screenshot widget keep as they get it. The icon loader's size
outputs, which no caller reads, are gone, and draw_cursor places the
cursor from the size word it is already given.
2026-09-22 22:08:56 +02:00
LibretroAdmin 144c75cfdf video: set_texture_frame takes the size word
The poke interface's set_texture_frame and every driver's
implementation of it take the menu texture's size as one packed word;
drivers that clamp it to their texture do so on the word's axes, and
Metal's menu view takes it as updateDims:. The threaded wrapper stores
the word it is handed, and rgui passes its framebuffer's word.
2026-09-22 22:04:08 +02:00
LibretroAdmin 053bdea488 menu: materialui's draw path carries one size word
The twenty-two draw helpers in materialui.c each took the output size
as a separate width and height, and each of them handed that pair
straight back to gfx_display, which packs it again. Both halves of the
round trip are gone: the helpers take a single video_dims word and pass
it through untouched.

Two of the call sites are function-pointer variables rather than
definitions -- materialui_render_menu_entry and
materialui_render_selected_entry_aux carry the callback shape -- so
their declared types move with the functions they point at.

The frame-level locals that fed these calls were only ever unpacked
copies of video_info->dims, so they go too, and the seventeen sites
that used them read the packed field directly.
2026-09-22 21:59:32 +02:00
LibretroAdmin 64282e4869 compile-matrix: wayland lanes probe for the generated protocol headers
The Wayland protocol headers are generated into gfx/common/wayland by
configure, so the Wayland lanes probe for them as well as for the
system's wayland-client.h, and the Vulkan one for vulkan.h;
platform_video takes a list of probes and skips on the first missing.
2026-09-22 21:54:15 +02:00
LibretroAdmin 5ed3a1fc8a gl2/gl3: the iOS/tvOS per-frame viewport reset takes the size word
b1e88d6f62 changed the set_viewport slot and every driver's own setter
to take the output size as one packed word, but the two TARGET_OS_IPHONE
calls in gl2_frame and gl3_frame still passed width and height as two
arguments. Those lines only build on iOS and tvOS, so no Linux, Windows
or macOS lane saw them, and the tvOS build stopped compiling.

Pack the two axes with VIDEO_SCALE_PACK, as the other call sites do.
2026-09-22 21:53:07 +02:00
LibretroAdmin dc4a4f132f gfx: the font renderer's flush takes the size word
font_renderer_t's flush and the GL, Vulkan, D3D11/12 and RSX
implementations of it take the output size as one packed word, as do
the GL and RSX font viewport setters behind them. render_msg builds
that word from the driver's own size once, and font_flush, the widgets,
xmb and the screensaver pass the size word they already hold.
2026-09-22 21:49:36 +02:00
libretroadmin d0c0b52693 cloud sync: progress reaches 100% when a sync toggle is off
Server and local manifest entries under a directory whose sync toggle
is off are carried forward rather than synced, and the diff steps over
them in one go. Progress now leaves them out of both the done and the
total counts, and is taken after each diff step rather than before it,
so the step that ends the diff also lands the bar on 100%. Previously
a disabled directory sorting last (thumbnails after saves and states)
was skipped in the final step and the bar stayed at the in-scope share,
e.g. 5% (#19606).

The directory-scope test moves into task_cloud_sync_key_in_scope(),
shared by the skip and by the count taken when the diff starts, so the
two cannot disagree.
2026-09-22 21:42:03 +02:00
LibretroAdmin 025f456b2a video: the overlay viewport's seqlock gets its release fence
The writer marks the stamp odd with a store-release and then writes the
four fields relaxed. A store-release orders the accesses before it, not
the ones after, so nothing stops those field stores from being hoisted
above the odd stamp. On a machine that reorders store with store - ARM,
PPC, so Android, iOS, the Apple Silicon builds, Wii, Wii U, PS3 - a
reader can then load an even stamp either side of a tuple that is half
written, which is the one thing the stamp exists to prevent.

The other two seqlocks in this file already carry the fence, and the
frame cache's says why in a comment. This one was the odd one out.

The harness reproduced the same shape, so it could not have caught
this; it carries the fence now. x86 does not reorder store with store,
so no lane there can make the hole visible - which is the point worth
recording next to it.
2026-09-22 21:41:03 +02:00
LibretroAdmin b1e88d6f62 video: set_viewport takes the size word
The video_driver_t set_viewport slot and each driver's own viewport
setter take the size as one packed word; the GL, Vulkan, GDI, RSX, GXM
and Metal setters store it as the viewport's full size directly, and
d3d8's rect helper returns the fitted viewport's word. Metal's
setViewportDims: replaces the width/height selector.

The menu drivers, widgets and screensaver hand set_viewport the frame
info's size word as they receive it.
2026-09-22 21:40:14 +02:00
LibretroAdmin 7d5b196aa3 ozone: the draw path takes the output size as one word
Sixteen of the driver's draw functions took video_width and
video_height as a pair. They take the word.

A hundred and fifty-eight repacks went with them - each one a
VIDEO_SCALE_PACK of two locals that came from the same word a few
lines above, which is the residue the layers below this one left as
they were packed. Ten locals had no reader afterwards and went too.
259 lines out, 205 in.

Matrix clean, the file native, under mingw and under C89, all at zero
warnings.
2026-09-22 21:31:45 +02:00
LibretroAdmin 39f38d8914 menu: the driver's render and OSK hooks take the size word
render, osk_ptr_at_pos and osk_pointer_over_textbox receive the output
size as one packed word, and the menu drivers pass it on whole: to the
screensaver, to materialui's entries box and to xmb's layout choice.
The menu drivers' init reads the output word without unpacking it, and
the pointer DPI cache keys on the same word.

The ozone_color_theme_defer sample hands the frame info's size word to
render.
2026-09-22 21:31:34 +02:00
LibretroAdmin b32af95295 dispserv_win32: cast QueryDisplayConfig to the local prototype
Builds without HAVE_DYLIB bind pQueryDisplayConfig to the SDK function
directly; it takes the file's own DISPLAYCONFIG structs, so it is cast
the way the GetProcAddress path already casts it.
2026-09-22 21:31:30 +02:00
LibretroAdmin 53c5d0ca36 xmb: the draw path takes the output size as one word
Six of the driver's draw functions took video_width and video_height as
a pair, and the draw context it fills for each item carried the same
pair again. Both take the word.

Thirty-six repacks went with them: each was VIDEO_SCALE_PACK of two
locals unpacked from the same word a few lines above, which is what
every call site turned into as the layers below this one were packed.
Three locals had no reader left afterwards.

xmb_draw_item reads its width and height off the context rather than
its own parameters, so it keeps a pair of locals - they are the halves
of the context's word now rather than two fields.

Matrix clean, the file native, under mingw and under C89, all at zero
warnings.
2026-09-22 21:25:50 +02:00
LibretroAdmin f88f2e31ea video: the display's output size comes back as one word
get_video_output_size - on the display server, the poke interface and
the context driver alike - fills one packed size word, as do the
frontend wrappers over it and the Cocoa and PS3 helpers behind their
implementations. Callers that set the video mode from it pass the word
straight through; the auto window cap keeps its limit packed.

The thumbnail sample stubs include video_defines.h for the size macros
they use.
2026-09-22 21:25:35 +02:00
LibretroAdmin d785da23da gfx: the display and widget helpers stop unpacking what they repack
Four helpers in gfx_display.c and gfx_widgets.c still took the output
size as a pair: the quad batcher, the indicator draw and the two
message draws. The batcher took a second pair for the quad's own size,
and its one caller was handing it VIDEO_SCALE_W(draw.dims) and
VIDEO_SCALE_H(draw.dims) - the halves of a word it already held.

With those four taking words, sixty-two repacks in the two files had
nothing left to do. Each was VIDEO_SCALE_PACK(video_width,
video_height) where the two locals came from VIDEO_SCALE_W and
VIDEO_SCALE_H of the same word a few lines above, so the word goes
through instead. Ten of those locals had no other reader and went with
them.

Matrix clean, both files under mingw, C89 pass, call_arity_check at its
baseline.
2026-09-22 21:20:53 +02:00
LibretroAdmin 4d896053af compile-matrix: wayland and webOS context lanes
The Wayland GL and Vulkan contexts, the GL context as webOS builds it,
and the webOS shim each get a lane. The shim's protocol headers are
wayland-scanner output of LG's webos-wayland-extensions (Apache-2.0),
kept under stubs/webos since the SDK only generates them at build time.

Every lane rejects incompatible pointer types and int-to-pointer
conversions, as gcc 14 does, so a callback whose signature no longer
matches its slot fails here and not only on newer toolchains.
2026-09-22 21:10:47 +02:00
LibretroAdmin cada67a3ef gfx: fpga init and fbdev EGL windows match what they are passed to
fpga_init takes the input driver pointer the video_driver_t slot hands
it, and the opendingux and vivante fbdev contexts cast their native
window to the pointer egl_create_surface takes, as vita and wayland do:
Khronos' eglplatform.h makes EGLNativeWindowType an integer on unix
builds without X11, which gcc 14 refuses to pass as a pointer.
2026-09-22 21:10:43 +02:00
LibretroAdmin 599cf50381 gfx: the font flush and the widget icon take their sizes as words
font_flush took the output size as a pair at all twenty-three of its
call sites, and gfx_widgets_draw_icon took two pairs - the output size
and the icon's own - at eighteen.

The icon draw gains the most: it was unpacking the icon pair only to
pack it straight back into the descriptor's dims, and packing the
output pair again for the draw call under it. Both go through whole.

The packs that appear at call sites here are the menu drivers' own
video_width/video_height parameters, which are the next pair along and
are left for their own change.

Matrix clean, C89 pass over every file touched, call_arity_check at its
baseline.
2026-09-22 21:10:27 +02:00
LibretroAdmin f8cd750b75 wayland/webos: size callback fills one packed word
The webOS get_video_size hands back a single packed size word like the
common Wayland one, and wayland_ctx.c's webOS prototypes for it and for
check_window match the context driver's get_video_size slot.
2026-09-22 21:03:10 +02:00
LibretroAdmin 41bacf060c gfx: three more context drivers read their size word
cocoa_vk's live-size query passes the word through, the WinRT window
size no longer shadows its out parameter, and the emscripten EGL init
takes its axes from the word it fills.
2026-09-22 20:58:55 +02:00
LibretroAdmin a59561a718 gfx: repair three context drivers' size-word reads
sdl3_vk and switch fill a local size word and take their axes from it;
the UWP context's local no longer shadows its out parameter, and the
Cocoa GL live-size query passes the word through.
2026-09-22 20:48:23 +02:00
LibretroAdmin 98fe9f6aa9 samples/gamemode: a clean target, and a skip without a built frontend
The unclaimed-samples job runs make clean before make check in every
sample directory and never builds the frontend. The gamemode sample
now has a clean target (run.sh keeps its stub in a temporary
directory it removes itself), and run.sh skips when there is no built
./retroarch to run.
2026-09-22 20:47:58 +02:00
LibretroAdmin 7673d8a171 gfx: the quad, the cursor and the keyboard take their sizes as words
gfx_display_draw_quad carried three width/height pairs in one
signature and two of them were output sizes with nothing in the names
to tell them apart: video_width/video_height went to the driver, and
width/height was the space the quad's coordinates are measured
against, which the body divides x and w by and flips y against. They
are video_dims and ref_dims now, and the quad's own w/h is dims.

At 133 of the 141 call sites the two are the same value passed twice.
The eight where they are not are the ones the names were hiding: the
menu screensaver measures against its own last size, and four draws in
xmb measure against the view rather than the window.

draw_cursor and draw_keyboard take the output size as a word with it,
which is the shape draw_texture_slice and the scissor calls already
had.

The line count is the argument lists: a pair that took two lines takes
one, so eighteen files lose 837 lines and gain 558.

Full matrix clean, the menu drivers and widgets under mingw, a C89 pass
over every file touched, and call_arity_check at its baseline.
2026-09-22 20:42:22 +02:00
LibretroAdmin e3151742e1 gfx: the thumbnail draw takes its sizes as words
gfx_thumbnail_draw takes the output size and the rectangle the
thumbnail is fitted into each in one word, and
gfx_thumbnail_get_draw_dimensions takes its rectangle the same way.
2026-09-22 20:42:12 +02:00
LibretroAdmin 2540f41fae gfx: the display driver's draw takes the size word
draw and draw_pipeline were the two members of gfx_display_ctx_driver_t
still taking the output size as a pair, beside the scissor calls that
already take it as a word. Both take it as one now, and so does
gfx_display_draw, which is the shim every caller reaches them through
and which passes the word straight down rather than unpacking it on the
way.

Twenty-two backends implement draw and six implement draw_pipeline.
Most read both halves and get them back from the word at the top of the
body; the few that read neither take the word and ignore it, as they
ignored the pair.

The call sites that still hold a pair of their own pack it at the call.
Those pairs are the next layer down - the gfx_display_ * helpers and
the menu drivers' own parameters - and are left for their own change
rather than dragged into this one.
2026-09-22 20:29:21 +02:00
LibretroAdmin 48a2bea0d5 gfx: the nine-slice draw takes its four sizes as four words
gfx_display_draw_texture_slice takes the output size, the texture's
size, the size to draw it at and the area it is placed in each in one
word; the body reads the axes it needs once at the top.
2026-09-22 20:23:46 +02:00
LibretroAdmin bd39245dd2 metal: the driver init reads the window size as one word
video_info_t's width and height became dims; metal.m's init was still
packing the two fields that had gone away. iOS and tvOS both stopped
there, six errors from the one line. applyVideoMode already takes the
word, and the other two callers already pass it.
2026-09-22 20:21:49 +02:00
LibretroAdmin 31a9a89382 x11: the pointer-entered flag and the mouse latch are atomic words
Under the threaded wrapper x11_alive() pumps X events on the video
thread while the input driver polls on the runloop thread, and both
touch g_x11_entered and the wheel/extra-button latch. Both are now
retro_atomic words; a wheel bit is read and cleared in one fetch_and,
so a notch latched between the two is kept.

The threaded-video harness gains an X11 event-pump lane: it sends
crossing and button events from its own connection while a frame is
on the worker and polls against it, so TSan sees the unordered
accesses on every run rather than when the server's pointer happens
to sit over the window.
2026-09-22 20:20:47 +02:00
LibretroAdmin a36955e3ac compile-matrix: the two SDL video drivers get lanes
Nothing here compiled sdl2_gfx.c or sdl3_gfx.c. That is how both went
on reading video_info_t's width and height after the pair had been
packed into dims: every job was green and the build broke.

Each lane skips where its headers are absent, as the dingux and exynos
lanes do, so the SDL2 one runs wherever libsdl2-dev is installed and
the SDL3 one wherever libsdl3-dev is. Putting the old field back fails
the SDL2 lane with the error the build gave.

sdl3_poke_set_video_mode gets the leftover from the interface change
with it: it took the size word apart into two locals and packed them
back together twice, once for the call it forwards to and once for the
field it stores, and nothing else read them. The word goes through.
2026-09-22 20:15:01 +02:00
LibretroAdmin bbdf94b1fc gfx: the scissor rectangle travels as two words
gfx_display_scissor_begin, the display context's scissor_begin and
scissor_end and all seventeen driver implementations take the output
size and the rect's size each in one word; a driver that needs the axes
takes them once at the top.
2026-09-22 20:14:29 +02:00
LibretroAdmin 0e9a40d2d9 qb: add --disable-gamemode
Configure builds can now leave out GameMode support with
--disable-gamemode; it stays enabled by default. Builds that do not
use config.h (the handheld Makefiles, griffin) keep GameMode as
before.
2026-09-22 20:10:14 +02:00
LibretroAdmin dc26c27bf7 GameMode: only leave GameMode if this process entered it
frontend_unix_deinit() leaves GameMode unconditionally, so shutdown
loads libgamemode.so.0 and queries gamemoded even with gamemode_enable
off. Track whether this process entered GameMode and make the leave
request a no-op when it did not.

samples/frontend/gamemode runs the built binary against a stub
libgamemode.so.0 and checks that nothing calls it with the setting
off, and that GameMode is entered and left with it on.
2026-09-22 20:10:10 +02:00
LibretroAdmin d4a816549d sdl: the stored window size is read and written as one word
sdl2 and sdl3 keep a video_info_t of their own; its size reaches the
raster font path and the resize through VIDEO_SCALE_W/H and
VIDEO_SCALE_PACK.
2026-09-22 20:09:07 +02:00
LibretroAdmin 4917564ea0 video: the context size comes back as one word
get_video_size was the last of the size interfaces still split: every
context driver wrote two unsigneds through two pointers, and every
caller supplied a pair of locals to catch them. Thirty-one
implementations, forty call sites, and the same shape at each one.

It writes the word now. The window checks are where that reads best.
Each of them used to hold a pair, fill it, pack it to compare against
the word the caller passed, and pack it again to store:

   unsigned new_width, new_height;
   get_video_size(x, &new_width, &new_height);
   if (VIDEO_SCALE_PACK(new_width, new_height) != *dims)
   {
      *dims   = VIDEO_SCALE_PACK(new_width, new_height);
      *resize = true;
   }

which is now one local, one comparison and one assignment. Eleven
drivers carried that.

Two of them were taking apart something already packed. The Cocoa
contexts publish their backing size to a thread-safe word as
(width << 16) | height, which is VIDEO_SCALE_PACK's layout exactly, and
their _ts readers existed to split it into the pair this interface
wanted; they hand the word over whole now. The Android Vulkan context
built its pair from two atomic loads and compared the halves
separately.

The driver locals that fed it go the same way: mode_width/mode_height
and temp_width/temp_height in gl1, gl2, gl3, vulkan, vg and gdi, which
is where the line count comes from. One test kept its two halves on
purpose - the fallback that asks video_driver for the output size when
the context reports nothing runs when either axis is zero, and the
packed word being non-zero does not mean both are, so it reads
VIDEO_SCALE_W() != 0 && VIDEO_SCALE_H() != 0 rather than dims != 0.

Full matrix clean, the Win32 drivers under mingw, the X11, EGL,
Wayland, DRM and OSMesa translation units the matrix does not reach
compiled here directly, and a C89 pass over every plain-C file the
change touches. The Cocoa contexts have no lane; their hunks were
reviewed by hand.
2026-09-22 20:08:12 +02:00
LibretroAdmin 4e31cd1cc7 menu: resetting the resolution keeps the window state
Start on the Resolution entry sets the video mode full screen only when
the frontend already is - from the setting or the forced-fullscreen
flag - so a windowed desktop session stays windowed; GameCube/Wii and
PS2 stay full screen as before.

samples/menu/resolution_reset links the shipping objects, swaps the
null driver's poke table for one that records the mode, and presses
Start in a windowed, a fullscreen-setting and a forced-fullscreen
session; it runs in the menu harness workflow.
2026-09-22 20:05:58 +02:00
LibretroAdmin c4be8acc48 d3d: drop the dead locals the overlay load and the queue drains leave
Assigned and never read: d3d8's overlay list pointer and the fence the
d3d12 queue drains take a copy of.
2026-09-22 20:03:29 +02:00
LibretroAdmin ff12e7fb6b video: the window size the drivers are initialised with is one word
video_info_t::dims carries both axes, so the size the frontend resolves
travels to every driver's init as one word and the drivers read the axes
they need with VIDEO_SCALE_W and VIDEO_SCALE_H. The Direct3D drivers'
stored copy compares and updates as one word.
2026-09-22 20:03:26 +02:00
LibretroAdmin 8f4180862f metal: implement set_video_mode
The poke sizes the window or takes it full screen through the same
-applyVideoMode: that driver init uses, on the main thread via
cocoa_main_thread_sync so threaded video can reach it, and shows the
cursor only when windowed - matching the Cocoa GL and Vulkan contexts.
A zero axis keeps the view's current size.
2026-09-22 20:00:19 +02:00
LibretroAdmin 12e54560f0 video: a context mode carries its size as one word
gfx_ctx_mode_t holds VIDEO_SCALE_PACK(width, height), and the Cocoa
GL/Vulkan contexts hand it through their main-thread argument blocks
the same way. The size copies those contexts kept but never read are
gone, as are the mode locals in gl1, vulkan and sixel that only
repeated the frame size or were never used.
2026-09-22 20:00:15 +02:00
LibretroAdmin 3245a3abc6 gfx: the display text draw takes the size word
gfx_display_draw_text, its high-precision sibling and the internal they
share take the area the text is placed in as one word; the menu drivers
and the widget layer hand over the word they already hold.
2026-09-22 19:49:56 +02:00
LibretroAdmin 0023598b0f video: a driver's window size is one local, not two
Six video drivers open with the same twenty lines: take the requested
size out of the frame descriptor, fall back to the detected screen size
when both axes are zero, fall back again to the configured fullscreen
size when fullscreen had to be forced, hand the result to the context
driver. With set_video_mode taking the size as one word, the two locals
that carried it between those steps have nothing to be two of.

The zero test is the part worth reading twice:

   if (video->fullscreen && (win_width == 0) && (win_height == 0))

becomes a test of the word, because neither axis set is the whole word
clear. The OpenVG driver gains the most - its fallback already asked
the video driver for a packed output size and took it apart to fill the
pair, and now assigns it.

gl1 and gdi keep their mode_width/mode_height, which are read
separately further down; they take the halves back out of the word.
sdl3_vk_ctx keeps its pair: it passes both to
sdl3_window_get_video_size by address, and a packed half has no
address, which is the same reason the settings viewport is not packed.

Full matrix, a native build and a C89 build from a clean tree, all at
zero warnings, plus the GDI driver under mingw.
2026-09-22 19:46:06 +02:00
LibretroAdmin 98028de03c video: setting the video mode takes the size word
Three interfaces in one call chain each carried the same loose pair.
video_driver_set_video_mode takes it from the frontend, hands it to
video_poke_interface's set_video_mode, and the video driver hands it on
to gfx_ctx_driver_t's - 59 declarations and 35 call sites, all of them
(unsigned width, unsigned height, bool fullscreen). The thread wrapper
already stored the pair packed in its mode packet and unpacked it for
no reason other than to make that call, which is what the chain looked
like from the inside.

All three take the size as one word now, and so do the two helpers
under them, win32_set_video_mode and sdl3_window_set_video_mode.

Most of the payoff is at the call sites. The thread wrapper passes
pkt.data.new_mode.dims straight through. d3d10, d3d11 and d3d12 were
unpacking their own viewport to pass the halves and now pass
->vp.full_dims. Twelve bodies that did nothing but forward the pair
pass the word instead of taking it apart and putting it back together.

sdl3_window_set_video_mode is why the rewriting worked from the
declarations rather than the calls: it takes five arguments and its
pair is not the one before the last, so anything that guessed the
position from the shape of the call would have packed its height
together with its fullscreen flag.

Full matrix, a native build and a C89 build from a clean tree, all at
zero warnings, plus the Win32 drivers under mingw. The Cocoa contexts
and the Metal driver have no lane here; their hunks are the parameter
rewrite and were reviewed by hand.
2026-09-22 19:46:01 +02:00
LibretroAdmin b8093d23d1 gfx: the widget frame path takes the size word
gfx_widgets_iterate, iterate_layout, the per-widget iterate callback,
gfx_widgets_draw_text and gfx_widgets_flush_text all carry the output
size as one word. The two scale caches compare one word, and the layout
check compares against last_video_dims directly.
2026-09-22 19:22:43 +02:00
LibretroAdmin 02102f12d3 menu: a metric rounds to the nearest pixel rather than the one below
Every menu metric is a constant times a DPI scale that is almost never
whole, and the plain conversion to the int it is stored in truncates.
That loses up to a pixel off each one, always downward, and it stacks:
at the 1.3333 scale Ozone's 50px row came out 66 rather than 67, which
is thirteen pixels over a twenty-row list. Ozone's three spacers
already rounded by hand - someone knew - while the other twenty-two
metrics beside them did not.

All of them go through VIDEO_PX now, in Ozone, MaterialUI and the two
integer metrics XMB keeps, so there is one way to turn a scaled length
into a pixel and it is the same one the draw origin uses. The 1px
spacer keeps its floor of one: a hairline that rounds to nothing is not
a hairline.

MaterialUI's node->entry_height is left alone deliberately. It is a
float that accumulates several terms before anything reads it, so
rounding each term into it would be rounding the same value twice.

Pixels move, which is the point: a metric that was a pixel short is a
pixel taller. ENTRY_HEIGHT gains one at the 1.3333 and 1.7778 scales,
ENTRY_ICON_SIZE at 1.7778 and 2.6667, HEADER_HEIGHT at 1.7778. At the
whole scales - 1.0, 1.5, 2.0 - nothing moves at all.

Full matrix, native build and C89 build from a clean tree, all three at
zero warnings.
2026-09-22 19:18:16 +02:00
LibretroAdmin 6a62c64930 gfx: a draw descriptor's origin travels as one word, in whole pixels
The descriptor carried its size as a packed integer pair and its origin
as two floats - the same quad's geometry in two number systems - and
every driver had to bridge that itself. None of them rounded. gl1, gl2
and gl3 hand the float to glViewport, which takes a GLint; gdi and d3d8
cast with (int). So a position that came off a scale factor and landed
at 99.99998 drew at 99, while its width was exact. gx2, d3d10, d3d11,
d3d12 and the two SDL drivers did not truncate at all - they divide by
the viewport to get normalised coordinates - so the same descriptor
drew at a different sub-pixel position depending on which driver was
up.

The origin is one signed pair now, in VIDEO_POS_PACK's layout, like a
viewport's. Whole pixels, decided once where the layout is known
instead of differently by each driver on its way out.

VIDEO_PX is what decides. It rounds rather than truncates, which is
worth a pixel on every value that came off a DPI scale, and it bounds
the conversion, which the plain cast does not: converting a float past
INT_MAX is undefined. That was not theoretical. gfx_widgets_draw_icon's
coordinate math underflows in the first frames after an icon loads, and
the float the descriptor carried took that value as far as
SDL_RenderGeometry, where it became NaN vertex positions and an SDL
error that poisoned the renderer state for every draw after it - the
widget simply vanished. sdl2_gfx and sdl3_gfx each carried a
hand-written range test against it. Both are gone: a signed 16-bit
origin and an unsigned 16-bit size cannot hold a value they would
reject, and a NaN reaching VIDEO_PX lands at the far edge, off-screen
and over the following frame.

The twelve positions that genuinely animate keep their float - the
tween is a pure function of the clock and wants sub-pixel motion - and
round on the way into the descriptor, which is where a sub-pixel scroll
position has to become a pixel regardless.

Checked with the full matrix, a native build and a C89 build from a
clean tree, both at zero warnings, plus the Win32 drivers under mingw
and sdl2_gfx against the host SDL2. metal.m and the 3DS and RSX
drivers have no lane here; their hunks are the mechanical read rewrite
and were reviewed by hand.
2026-09-22 19:18:12 +02:00
LibretroAdmin 1c26684ad1 threaded_video: the 4K surface lane checks the mean submit time
Thread CPU time on Windows advances in whole 15.625 ms scheduler
ticks, so one submit that straddles a tick reads as a full tick and
fails a per-sample percentile. The lane now budgets the mean over all
submits, which tracks the time actually spent on any clock and still
fails when every submit carries a 4K copy. The p50/p95/p99 figures
stay in the baseline line.
2026-09-22 19:04:08 +02:00
LibretroAdmin fe9a3325a7 rgui: the confirm dialog's icons are placed in whole pixels
RGUI blits into an integer framebuffer, and these were the only two
floats in the file. They existed to halve a glyph: icon_padding is
font_width_stride / 2, and every offset built from it was truncated
back to int one to five lines later.

Most of those halves cancelled - the padding appears in pairs that add
back up to a whole glyph - so the icon origins were always integers
taking a detour through float. The two that did not cancel are the hit
box's top edge and its height, and there the truncation threw the half
away. The integer forms keep exactly what the truncation produced:
(size + 1) / 2 for the edge, (size * 5) / 2 for the height, checked
against the float arithmetic across every font stride from 1 to 39 and
every framebuffer geometry RGUI runs at.

No pixel moves. The point is that a software blitter has no half
pixels to place anything on, so nothing here should be asking for one.
2026-09-22 18:39:19 +02:00
LibretroAdmin 3017b2015a gfx: Ozone's scale cap divides by three rather than by a literal
The cap is a third of the screen width, written as a multiply by
0.3333333f. That literal is one ulp below the nearest float to 1/3, so
the cap lands just under the third it is meant to be - 639.99994 rather
than 640 at 1920 wide - and the scale that comes out of the division is
off by the same amount. Dividing says what it means and is exact to the
float.

The name went with it. new_width is not a width of anything; it is the
largest the sidebar is allowed to be, which is what the code below it
compares against.
2026-09-22 18:35:10 +02:00
LibretroAdmin b4d16d67bc video: the frame info's output size travels as one word
video_frame_info_t::dims carries both axes beside the scale_dims and
custom_vp_dims words already there, and the two writers hand over the
packed word they already held. The widget tick takes it straight
through; the netplay widgets read their axes once into locals.
2026-09-22 18:30:43 +02:00
LibretroAdmin bd6bcc21d3 gfx: the animation tick and the DPI scale take the size word
gfx_animation_update, gfx_animation_update_widgets, the update_time_cb
the menu drivers set, and gfx_display_get_dpi_scale all take both axes
in one word. The scale caches compare one word where they compared two,
and the widget path hands its packed size straight through.
2026-09-22 18:30:40 +02:00
LibretroAdmin b3b05b5ef1 compile-matrix: the PS2 video driver gets a lane
ps2_gfx.c is the other console video driver nothing here compiles, for
the same reason as the Vita one: it reaches a compiler only inside the
PS2 toolchain. The viewport work touched it in two places, and neither
has been through a compiler.

This adds the gsKit and PS2SDK stubs it needs - gsKit.h with dmaKit.h
under it, kernel.h for the semaphores its vsync handler takes, and
tamtypes.h for the scalar names the rest lean on - and a lane that
compiles it against them. gsToolkit.h and gsInline.h are the two other
headers the driver includes; gsKit ships both behind its master header,
so here they are one line each.

Same rule as the Vita stubs: the declarations the driver names and only
those, each in the shape the real header gives it, so the lane rejects
what the SDK would reject. Dropping an argument from
gsKit_prim_sprite_texture fails it.

The lane takes the window offset, which Makefile.ps2 turns on and the
driver reads; without it the settings struct has no such member and the
lane would fail on the tree rather than on a change to it.

It is also the only lane that compiles retro_atomic.h's EE
interrupt-mask backend. No other platform selects it, so DIntr/EIntr
and every operation built on them were unreachable from any job here.

ExitHandler() is the one declaration whose body cannot run on the host:
it is the R5900 sequence the SDK writes, and the lane gets away with it
because it parses rather than assembles. A lane that compiled to an
object would need a stand-in; the note in kernel.h says so.
2026-09-22 18:29:27 +02:00
LibretroAdmin f12c36929a compile-matrix: the Vita video driver gets a lane
gxm_gfx.c reaches a compiler only inside the Vita toolchain. Nothing
here builds it, so a change to a struct or a signature it touches is
green in every job until that console job runs - which is how four
lanes broke in a row on the viewport work, one round trip at a time.
The driver is one of the files that work rewrote most: its viewport
origin, its two size pairs and the output size all travel as packed
words now, and none of that has been through a compiler.

This adds the psp2 stubs the driver needs - gxm.h, display.h,
kernel/sysmem.h, common_dialog.h, and the scalar types the rest lean
on - and a lane that compiles it against them.

The stubs carry the declarations gxm_gfx.c names and only those, each
in the shape the real header gives it, so the lane rejects an argument
list the SDK would reject rather than only a malformed one. Dropping
an argument from sceGxmSetRegionClip fails the lane; so does dropping
one from the driver's own gxm_set_viewport. The enums keep the SDK's
values and lose the members nothing here names, which is why a format
constant may be missing: take it from the real header rather than
inventing a value.

Same narrowing suppression as the psp1 lane above it - the driver
folds pool addresses into pointers that a 64-bit host widens, and the
arithmetic is 32-bit on the target.
2026-09-22 18:29:22 +02:00
LibretroAdmin 22a0a7a43d compile-matrix: the WiiU and HUB75 video drivers get lanes
Both compile here already - gx2_gfx.c against the wiiu headers the tree
vendors, hub75_gfx.c against plain POSIX - so the only reason neither
was checked before delivery is that neither had a lane. They are the
drivers this campaign kept breaking unseen.

HOSTOFF moves up to the flags every lane shares. It was defined two
hundred lines below the first platform_video lanes, so a console lane
written above it expanded to nothing and compiled with the host
identity left on, taking the Linux branch of every #if ladder and
proving nothing.
2026-09-22 17:44:08 +02:00
LibretroAdmin 9803e3eadc vg: the OpenVG driver's init reads the output size again
vg_init's fullscreen branch takes the window size from the output word
and writes it straight to win_width/win_height; its two declarations of
out_dims become one. The driver has had neither since the output size
started travelling as one word: nothing here compiled it, so the
statements left in the middle of an if condition went unseen.

tools/platform_stubs/openvg carries the OpenVG surface the driver names
- generated from what it uses, not from the specification - and the
compile matrix gains a lane for it, so the next change to this driver
is compiled rather than read.
2026-09-22 17:31:57 +02:00
LibretroAdmin 481b5050d1 video: the output size's threading note names the atomics
The pair is published with a release store and read with an acquire
load, which is what makes both calls safe from any thread.
2026-09-22 17:26:03 +02:00
LibretroAdmin 3ec31e2ddd tools: a call's argument count is checked in every platform branch
A signature change is caught by the compiler wherever it compiles. The
call sites it does not compile are found by whichever CI lane builds
that platform, one round trip each: changing check_window's parameters
broke the PS3 driver, two Win32 contexts and the webOS window check
that way, one after another. tools/call_arity_check.py reads the tree
as text instead, so a branch behind GEKKO, WEBOS, VITA or _XBOX is
examined like any other, and reports a call whose argument count does
not match the declaration.

It declines what text cannot settle: varargs, calls through a pointer,
function-like macros, an argument list carrying a preprocessor
conditional, and any name a harness stub redeclares with a shape of its
own.

Its first finding goes with it. gx_efb_screenshot() called
intfstream_open() with intfstream_open_file()'s three arguments, so the
GameCube and Wii builds would not have compiled it - nothing did: it
sat behind a TAKE_EFB_SCREENSHOT_ON_EXIT that an "#if 0" defines, and
nothing called it. It goes, with the stream include it was the only
user of.
2026-09-22 17:23:14 +02:00
LibretroAdmin b6222d4505 wayland: the webOS window check takes the size word
gfx_ctx_wl_check_window_webos is the webOS build's stand-in for the
shared handler, so it takes the same one word and compares it whole.
Its declaration in wayland_ctx.c follows.
2026-09-22 17:11:46 +02:00
LibretroAdmin 40e2fa3c88 (Vulkan) Negotiation interface v3 is withdrawn; the frontend presents on the graphics queue
The frontend stops presenting on a queue of its own, and the negotiation
interface goes back to version 2. This removes 5d950a7288 (the second
queue), dadbd009d9 (v3 in libretro_vulkan.h), c265d78ee9 (the Android
carve-out) and 9f686efdfa (the comment on when it is used), and adapts
4823918abe, which stays.

What it bought was narrow: on one vendor, in exclusive fullscreen,
vkQueuePresentKHR blocks inside the call waiting on the display, and
with one queue under queue_lock a core's submissions sat behind that.
What it cost is on record. Its first act was to break every frontend
older than four days against cores that took the queue (libretro/ps2
#167), it needed a per-platform exception within five days because a
window system could not present on the second queue, and it is the only
change in the path where the same issue now reports random aborts at
start and heap corruption at exit - on the v3 path every time. None of
that is proof, and none of it is a measurement in its favour either.
Three places had to agree about it, and the feature was on for some
systems and silently off for others. That is not worth the stall it
removed.

4823918abe - a swapchain rebuild waits out the presents it retires - is
kept, because the bug it fixes is not the second queue's. A present's
semaphore wait is a queue operation behind the last frame's fence, not
under it, on any queue; Mali's Android WSI (#19601) fails every later
present when the semaphore goes out from under one. The empty
submission that fences the presents now goes to the graphics queue,
under queue_lock like any use of it.

A core that asks for version 3 gets 2 and the graphics queue as its
presentation queue, which is what every build before the 17th did.
The vulkan_validation sample builds and passes, the teardown-behind-a-
present case included, on llvmpipe here.
2026-09-22 17:00:47 +02:00
LibretroAdmin 0b2613d40a gfx: the Win32 get_video_size calls take the size word
wgl_ctx and uwp_egl_ctx ask win32_check_window for the size and hand
their own two out-parameters back from its halves. Both also write
through those parameters rather than over them, so the Xbox display
resolution reaches the caller.

tools/mingw_syntax_check.sh reports these files at all now. A header
this box lacks made the compile stop at the include, and the
missing-header filter then reported the file clean: HAVE_D3DKMT pulls
in d3dkmthk.h, so every consumer of win32_common.h went unchecked that
way. The define is dropped when its header is absent, and a parse that
stops early is named and counted rather than folded into the ok line.
2026-09-22 16:57:39 +02:00
LibretroAdmin e57668d229 Derive swap intervals above four, bounded by what the driver presents
'Auto' derives the swap interval as the whole multiple the display rate
is of the content rate, up to MAXIMUM_SWAP_INTERVAL, which the manual
setting's range reads too so the two cannot drift apart. A 480 Hz panel
running 60 fps content holds each frame for eight display frames;
driver_adjust_system_rates() then divides the target by that interval
and the audio rate follows the display.

The multiple has to be one the driver can present: the D3D APIs carry
the interval as a present parameter that stops at four, so those drivers
report that bound through the new get_swap_interval_cap poke and the
ceiling comes down to it. A multiple past the cap falls back to 1 rather
than clamping short, since a short multiple paces the loop fast.

The decision itself moves to runloop_video_swap_interval_for() in
runloop.h, next to the other pure pacing decisions, and
samples/runloop/pacing covers the multiples, the driver cap, the ratios
that are not whole multiples, and the degenerate rates.
2026-09-22 16:47:21 +02:00
LibretroAdmin 676d426e45 gfx: the window check's size reaches every caller
Six drivers read the output size in the else branch of their init:
that branch takes the three statements it needs as a block. The Win32
Vulkan context, the SDL3 Vulkan context and the SDL3 shared handler
pass their own size word on rather than the two names it replaced.
2026-09-22 16:41:03 +02:00
LibretroAdmin 944fb12100 wayland: the toplevel icon and its buffer are released once the icon is set
xdg-toplevel-icon-v1 lets the client destroy the icon object right
after set_icon without the toplevel losing its icon, and the buffer
only has to outlive the icon object; wl_buffer.release is never sent
for it. So wl_create_toplevel_icon() now destroys both straight after
set_icon, and nothing about the icon has to live with the window.

That drops the libdecor_icon, xdg_toplevel_icon and icon_buffer fields,
the shm_buffer back-pointer and the release handler special case. The
failure path also destroys the icon object instead of leaking it.
2026-09-22 16:38:48 +02:00
Davey Hughes ff0000f895 wayland: the libdecor objects and the icon buffer are freed with the window (#19609)
gfx_ctx_wl_destroy_resources_common() released every other Wayland
object but left the libdecor frame, context, toplevel icon and dlopen
handle behind, so every video driver reinit dropped a set and built
new ones. Release them before the surface is destroyed and the display
is disconnected: the frame owns xdg objects built on that surface, and
the context still talks to the display.

The icon's shm buffer leaked the same way. It is freed on
wl_buffer.release, which a compositor that holds the icon need never
send, so the window owns it now; an early release clears the
reference.
2026-09-22 16:37:19 +02:00
LibretroAdmin dc44cc7b0a video: the output size travels as one word, in and out
video_driver_get_output_dims() hands back the word the state already
holds and video_driver_set_output_dims() takes one, so a reader gets a
coherent pair from a single atomic load rather than two halves split
out of it. VIDEO_DRIVER_OUTPUT_WIDTH/HEIGHT were aliases for
VIDEO_SCALE_W/H and go with them.

The window check hands its size back the same way: check_window on the
context-driver vtable, win32_check_window and x11_check_window each
take one unsigned* where they took two, and a driver passes the field
itself - d3d10, d3d11 and d3d12 give &d3dN->vp.full_dims straight to
win32_check_window. The drivers' own size locals travel packed
alongside, and the runloop's two aspect checks compare one word
against the size they last laid out for.
2026-09-22 16:32:54 +02:00
libretroadmin 9383fe9628 gfx_thumbnail_preview: the audio chain against ffmpeg
The preview's sound had been held to itself - decoded, fed, not
stalled - and never to a reference, while its first seconds are
reported as distorted in the field. preview_audio_oracle_test decodes
a track through the chain a preview uses, audio_mixer to
audio_transfer to the demuxer to the AAC decoder, from the first
sample against ffmpeg's decode of the same track: at the track's rate
the two agree to the rounding of a float decoder, and through the
mixer's resampler, the filter's delay aligned for, to a third of a
percent RMS in every half second from the first. So the chain from
the file to the mixer's output is not where the first seconds go
wrong; what remains is downstream of the mixer, or the feed. In the
check target and CI.
2026-09-22 16:05:11 +02:00
Antonio Orefice fc5bf86d63 Change logging verbosity for "optional include not found" shader compilation message from info to debug. (#19608)
"Optional includes are not widely used across shaders and require user tinkering, so I think it is better to cut the noise"
2026-09-22 15:41:38 +02:00
LibretroAdmin 289b74d8b9 gl3: the shader chain's viewport travels as two words
gl3_viewport is this backend's own, so its origin and its drawn area
pack like the frontend's and glViewport, glScissor and the pass sizing
take the halves back out. gl3_set_viewport hands the frontend viewport
over as two word copies.
2026-09-22 15:37:54 +02:00
LibretroAdmin 7d1cf55a0a save: drop the unreferenced autosave dirty marker
Nothing in the tree calls it and no header declares it; the dirty bit
is set where the autosave buffers are compared.
2026-09-22 15:37:40 +02:00
LibretroAdmin d460d0ec9f rsx: the RSX viewport travels as two words
rsx_viewport_t is this driver's own, so its origin and its drawn area
pack the same way the frontend's viewport does and rsxSetViewport takes
the halves back out. A viewport handed straight over is a word copy -
rsx_set_viewport and rsx_frame take vp.dims from rsx->vp.dims as it
stands. The depth range, scale and offset stay floats: they are not
pixel counts.

tools/viewport_shadow_check.py reports the remaining names that mean a
video_viewport_t in one place and another type elsewhere, skipping the
ones already packed, so what is left is the types whose layout is not
ours to change - VkViewport, MTLViewport, wp_viewport and the GL
handles.
2026-09-22 15:06:09 +02:00
LibretroAdmin 138dcfefc6 rsx: the RSX viewport keeps its own origin
rsx_viewport_t is the RSX's own float x/y/w/h that rsxSetViewport and
rsxSetScissor take; only rsx->vp and gcm->vp are the frontend's packed
viewport, so the local takes its two axes out of those and passes them
on as floats.

tools/viewport_shadow_check.py reports the names this confusion needs:
one that means a video_viewport_t somewhere in the tree and some other
type in the file at hand. It names rsx_viewport_t, VkViewport,
MTLViewport, gl3_viewport, wp_viewport and the GL handles, by
declaration rather than by compiling, so the drivers no lane here
builds are covered too.
2026-09-22 14:58:13 +02:00
LibretroAdmin 05292a712d companion: poll() reads the published session without the lock
An atomic beside anim_sess says whether the animation thread has one
published, so a poll with nothing playing takes no lock at all. The
audio window is still started and fed under the lock, where the
session stays valid.
2026-09-22 14:54:33 +02:00
LibretroAdmin 2967bab24f tpool: the queue depth and the outstanding count answer without the mutex
Two atomic counters track the items in the queue and the items queued
or in progress, written under work_mutex beside the queue itself and
read without it. tpool_help returns on an empty queue and tpool_wait
returns on an idle pool without touching the lock, so a caller looping
in either while it waits on work in other hands leaves the queue's
lock to the threads that need it.
2026-09-22 14:54:28 +02:00
LibretroAdmin a9dfba8ac9 menu: the laid-out sizes the drivers cache travel as one word
Every menu driver keeps the video size it last laid out for and
compares the incoming one against it; that pair is one word in
VIDEO_SCALE_PACK's layout, so the check is a single compare against
last_dims rather than one per axis. RGUI's glyph cell, its window size
and its mini-thumbnail cap, and MaterialUI's nav-bar layout go the same
way.

The pairs whose axes are derived apart from each other keep two
members: the widgets' bg_width/bg_height come from text metrics and
line height separately, and MaterialUI's thumbnail caps are clamped one
axis at a time, so packing either would buy one store and cost eleven
read-modify-writes.
2026-09-22 14:52:58 +02:00
LibretroAdmin 5ea7612b07 video: the viewport's origin travels as one word
video_viewport_t holds x and y in VIDEO_POS_PACK's layout, x in the high
half and y in the low, each a signed 16-bit offset that sign-extends
back out through VIDEO_POS_X/Y - an origin goes negative wherever
integer scaling overscans the window. VIDEO_POS_PUT_X/Y set one axis
where a caller has only that one, and an offset past a half saturates,
so neither axis can put the image on the far side of the display.

The whole viewport is now three words. The threaded wrapper's seqlock
publishes the origin as one slot, so VIDEO_THREAD_VP_SLOTS drops to
three and a reader takes three relaxed loads between the sequence
checks. VkViewport keeps its own float x and y, as does the overlay's.

samples/gfx/viewport_pack gains an origin lane: the round trip over
negative offsets, a sign bit that stays in its own half, one axis put
without moving the other, and the saturation. Dropping the sign
extension from either half fails it six times.
2026-09-22 14:34:22 +02:00
LibretroAdmin 72d39a2c00 gfx: the Win32 viewport reads reach the packed word
d3d9_make_d3dpp takes the back buffer's size out of d3d->vp.full_dims,
and the GDI window's paint handler takes its viewport rect out of
gdi->vp.dims. Both files reach a viewport through a member their own
header declares, so neither names video_viewport_t.
2026-09-22 14:09:28 +02:00
LibretroAdmin 89fe288c71 video: the viewport's two size pairs travel as one word each
video_viewport_t carries the drawn area and the window that holds it in
VIDEO_SCALE_PACK's layout, one word each, so a viewport reaches a driver
in two stores and every producer holding both axes writes them together.
VIDEO_SCALE_PUT_W/H set a single axis where a caller has only that one,
and a viewport passed straight through is a word move per pair - the GL
backends hand gl->vp.dims to their shader parameters as it stands.

The custom viewport the settings own keeps its axes as two members,
under video_viewport_settings_t: configuration.c's SETTING_UINT rows and
the menu's offsetof rows bind an unsigned by address, and half a word
has no address to give them. video_driver_init_internal() seeds it
through a viewport of the packed type and takes the two axes back out.
VkViewport and MTLViewport keep their own width and height.

samples/gfx/viewport_pack holds the layout: the round trip on both
words, one axis put without disturbing the other, and the clamp that
keeps an oversized axis out of its neighbour's half. Transposing the two
halves fails it 29 times.
2026-09-22 14:00:00 +02:00
LibretroAdmin 25c0612d23 (h265) Publish rows through an eventcount
Row publication notifies a retro_eventcount: a release store for the
row count and a notify that touches no lock unless a thread is parked.
The reference-row wait, the DPB output wait and the context join
register, re-check their own predicate and only then sleep. The
lossless sample links retro_eventcount.c for its threaded build and
keeps it out of the no-threads one.
2026-09-22 13:59:54 +02:00
LibretroAdmin 733d78bf27 (h264) Publish rows through an eventcount
Row publication notifies a retro_eventcount: a release store for the
row count and a notify that touches no lock unless a thread is parked.
The three waits on it - a reference picture's rows, the output pop and
the context join - register, re-check their own count and only then
sleep. The lossless sample links retro_eventcount.c for its threaded
build and keeps it out of the no-threads one.
2026-09-22 13:59:51 +02:00
LibretroAdmin 9f686efdfa libretro_vulkan.h: a v3 presentation queue is accepted, not necessarily used
The frontend may present on queue even when a core hands back another
queue of the graphics family: where the window system is known not to
tolerate a present on another queue, it does, still serialised with the
core's submissions through lock_queue/unlock_queue. Say so, so a core
does not depend on the separate queue being used. Comment only.
2026-09-22 13:19:08 +02:00
LibretroAdmin c265d78ee9 (Vulkan) Android presents on the graphics queue
Since 5d950a7288 the frontend presents on the graphics family's second
queue wherever the family has one. Behind Android's WSI the loader
turns a present's wait semaphore into a sync fd through the driver's
QueueSignalReleaseImageANDROID, and Mali (G615 MC6, r44p1, which
reports two queues in family 0) fails that call when the semaphore was
signalled on another queue: every present fails, the failed present
destroys the swapchain, the next acquire rebuilds it, and the menu
freezes in that loop (#19601). A CI build from before 3df4bd91b7 - the
swapchain-teardown hazard fixed in 4823918abe - already froze, so that
fix alone does not cover it.

The choice is made in one place now, vulkan_select_present_queue_index():
queue 1 where the family has a second queue, queue 0 behind Android's
WSI. Android's WSI is VULKAN_WSI_ANDROID, and any context driver in an
ANDROID build, the SDL3 one included. A core that provides its own
presentation queue in the graphics family gets the graphics queue
there as well; one in another family is still refused as before. The
desktop drivers keep the second queue and its lock-free presents.

The vulkan_validation sample checks the policy ahead of its display
check, so it runs on every lane: second queue elsewhere, the graphics
queue behind Android's WSI at one, two and four queues. The old
selection behind the same helper fails it on the Android cases.
2026-09-22 12:20:19 +02:00
LibretroAdmin 4823918abe (Vulkan) A swapchain rebuild waits out the presents it retires
A present is a queue operation that vkQueuePresentKHR returns ahead
of: the presentation engine still has to wait on the frame's swapchain
semaphore, and no frame fence covers that wait - the fences are
signalled by the render submission, which the present comes after.
Since 5d950a7288 the present goes to a queue of its own where the
family has one, with nothing on it that a frame fence orders, and
since 3df4bd91b7 a swapchain rebuild or teardown waits on the frame
fences instead of draining the device. Together, a resize or an
out-of-date acquire destroyed the swapchain semaphore, and the
swapchain, under a present still waiting on them.

Mali's Android WSI (r44p1, MT6897) does not survive that: every
present after it fails inside QueueSignalReleaseImageANDROID, the
failed present destroys the swapchain, the next acquire rebuilds it,
and the menu freezes in that loop (#19601: the 09-16 nightly clean,
09-18 failing every frame).

vulkan_context_wait_frames() now also waits out the presents when one
has been queued since it last ran: an empty submission on the present
queue, behind them, signals a fence the rebuild waits on with the
frame fences. One submission per rebuild, nothing per frame, and only
the present queue - a hardware core's work is still not drained. The
shared-queue case takes queue_lock for the submission as any use of
that queue must.

The vulkan_validation sample gets the case: acquire, a fenced
submission signalling the swapchain semaphore, the present, and a
teardown behind it. The validation layer retires a present's waits
when vkQueuePresentKHR returns, so it passed the old code; the sample
now tracks presents on the symbol wrapper's function pointers and
fails when a semaphore or swapchain a present is queued against is
destroyed before a fence behind that present has been waited on. Fails
on the old code (both objects), passes on this.
2026-09-22 12:03:06 +02:00
LibretroAdmin b0a070c746 compile-matrix: the Vulkan and X11 back-end lanes skip without headers
They go through platform_video with the header each one needs as its
probe, so a runner without the Vulkan or X11 development headers says
so and the matrix passes, the way every other lane whose headers may be
absent already does.
2026-09-22 03:27:43 +02:00
LibretroAdmin 6eadbc07a4 compile-matrix: lanes for the back ends a desktop job does not select
A Linux desktop configuration builds no Vulkan, no Cg and none of the
framebuffer or X11 back ends, so a change to the frame descriptor or the
shader parameter block those read is green here until a job that selects
them runs. Five lanes compile them, and the UI companion's own tests
join its core.
2026-09-22 03:08:48 +02:00
libretroadmin 7420784664 settings: the animated-thumbnail threads help says what the threads do now
The setting's help described the threads as converting frames to
pixels, which was all they did when it was written; they carry the
decode itself now, and the preview keeps to one thread while a core
is running. The help says so, in terms of what turning it up buys
and what it costs, and names no format.
2026-09-22 03:05:38 +02:00
libretroadmin 18a18ae91e rh264: catching up drops pictures with the pool busy too
With pictures decoding concurrently the decoder declined catch-up
drops, on the argument that a non-reference picture runs beside the
chain of references and dropping it buys no time. That holds when the
threads outnumber the pictures in flight and nowhere else: on a
machine whose pool is saturated by a 4K stream, the non-reference
pictures are half the work, and a preview that fell behind stayed
behind. The drop now happens after the slice header is parsed and
before any of the picture's state is taken on - the first slice of a
nal_ref_idc 0 picture and every slice after it - which leaves the
decoder exactly as it was, one picture at a time or four in flight,
as the HEVC decoder already did.

The catch-up oracle gains a build with four pictures in flight on a
pool, and CI runs both - it had built the oracle and never run it.
Every picture shown while catching up is the full decode's, at the
same count threaded as not, under ASan and TSan.
2026-09-22 03:02:18 +02:00
LibretroAdmin 584bc57007 gfx, companion: the Cg backend and the thumbs test read the packed sizes 2026-09-22 02:59:25 +02:00
LibretroAdmin 56b34f5a52 video: the frame descriptor's scale and custom viewport are one word each
video_frame_info_t carries the content scale and the custom viewport as
packed words. video_thread_get_scale() hands back the word the video
thread published rather than splitting it for a caller that stores it
whole, so the threaded path carries the scale across without touching
either half.

The custom viewport's full size went unread by anything, and goes.
2026-09-22 02:54:04 +02:00
LibretroAdmin 83f434af39 gfx: the shader parameter block's four size pairs travel as one word
video_shader_ctx_params_t carries the viewport, input, texture and
output sizes as four packed words, so the GL drivers fill four members
per pass and the GLSL backend unpacks them once into the locals its
uniforms are built from.
2026-09-22 02:39:18 +02:00
libretroadmin 128443f9fb rvp9: the tile jobs are waited on by their count, not by the pool falling idle
The last of the three fan-outs that waited for the whole pool to go
idle after handing it a frame's work: the blit's bands and the HEVC
wavefront went before, and the VP9 tile columns go now. The frame's
jobs share a count; the last to finish wakes the caller, who takes
queued work from the pool meanwhile rather than sit idle, and waits
for the frame's own jobs and nothing else. Harmless while VP9 was
alone on the pool, a stall per frame the moment anything shares it.
The tiled fixture hashes the same at every thread count, under ASan
and TSan.

Two built harness binaries had been committed - the ignore pattern
covered <dir>_test and not the _test_threaded and _test_norwebm
names beside it - so every make clean there showed as a deletion;
they are untracked, and the patterns cover names built on _test_ too,
sources excepted.
2026-09-22 02:30:09 +02:00
LibretroAdmin 765eea22af compile-matrix: the UI companion's shared core gets a lane
companion_core, companion_thumbs and companion_dock reach a compiler
only in a Qt or Cocoa build, so a desktop job configured --disable-qt
compiles none of them and a change to a struct they read stays green
until one of those runs. Two lanes compile all three, with and without
threads.
2026-09-22 02:22:33 +02:00
libretroadmin 348341a4e9 rh264_lossless_test, rh265_lossless_test: a build without threads runs the oracle too
The last patch found the HEVC decoder applying SAO to nothing in a
build without threads, the flag that asks for it having been set
inside a HAVE_THREADS block; the oracle could not have caught it,
being built with threads only. Each oracle's Makefile now builds the
same test a second time without HAVE_THREADS - no pool, no rthreads,
the concurrent legs skipped, every one-thread and rotated leg held
to ffmpeg as before - and the CI sample runner runs both. A decoder
path right only with threads, or wrong only without them, shows in
this lane rather than on a DOS or console build.
2026-09-22 02:18:40 +02:00
libretroadmin 24cff0686d rh265: the wavefront fan-out goes; pictures on the pool are the threading
Since pictures decode concurrently, a pool given to the decoder is
the pictures', and the wavefront fan-out - a picture's CTB rows dealt
to the pool, with shadow decode states, row progress counters and
the group on an eventcount - could no longer be reached: the two
threadings shared the pool and the one for pictures won, being ×N on
any content rather than on wavefront-coded content alone. What could
not be reached goes, 350 lines and the eventcount dependency with
it; wavefront-coded pictures decode their substreams in order as
they always did on one thread, and the fixture's hash is the same.

With it a bug that came in with the row hook: the flag that says a
picture asked for SAO was set inside the fan-out's HAVE_THREADS block,
so a build without threads applied SAO to nothing. It is set where
the slice data starts, in every build. The header describes the
decoder as it is - Main and Main 10, P and B, WPP, bypass, constrained
intra, pictures concurrent - and what it still refuses.
2026-09-22 02:14:30 +02:00
LibretroAdmin eaf3d788bf companion_thumbs: the preview's size comes from the packed word 2026-09-22 02:13:34 +02:00
LibretroAdmin c741a3331e gfx: the preview and line-ticker size pairs travel as one word
gfx_anim_preview_t carries the animation's size as one word, packed
from the two the stream fills, and gfx_animation_ctx_line_ticker_smooth_t
its field size. gfx_anim_preview.h takes video_defines.h along, so a
consumer of the header has the accessors.

The plain smooth ticker keeps its field_width: it has no height beside
it, so there is no pair there to fold.
2026-09-22 02:10:36 +02:00
libretroadmin d17b578687 rh264: interlaced coding in the oracle and the preview fixtures
The oracle had no interlaced case: pair scanning, field motion
compensation, the per-pair deblocking and, with pictures in flight,
the rows of a MBAFF picture published only at its completion, were
held to nothing. Three x264 MBAFF cases join it - CAVLC, CABAC with a
B-pyramid, weighted prediction - decoded in all four modes against
ffmpeg, and an MBAFF fixture joins the preview oracles, which run it
plain and concurrently under both sanitizers. All byte-exact as the
decoder stands; a comment claiming B field pictures refused, stale
since the field machinery landed, goes.
2026-09-22 02:08:18 +02:00
libretroadmin 854f7de0be rh265: transquant bypass and constrained intra prediction
Two Main-profile features the decoder refused, so that a stream using
either got no picture at all. Transquant bypass: the CU's flag is
parsed, its residual is its coded levels with no scaling and no
transform, its transform_skip_flag is not read and its signs are not
hidden, and the loop filters leave its samples as they are - the
deblocking filter runs the edge and puts the bypass side back, SAO
passes the samples over. Constrained intra prediction: a reference
sample from a CU that is not intra-coded is unavailable to intra
prediction, the motion field's pred saying which. Byte-exact against
ffmpeg in the four decode modes: lossless 8-bit, 10-bit, with the
wavefront and with B-frames, bypass CUs mixed with coded ones under
deblocking and SAO, and constrained intra in P and B pictures; the
streams that never used either decode as before, to the hash.
2026-09-22 01:49:23 +02:00
libretroadmin d2cbd6114c rh265: an oracle against ffmpeg, and the row hook filters a row behind
HEVC decoding is specified to the sample, so for any conforming
stream the decoder's output is ffmpeg's to the byte, lossless or not;
the decoder had only ever been held to its own output across modes.
rh265_lossless_test builds its cases with x265 - deblocking and SAO,
B-frames and a pyramid, the wavefront and none, 10-bit, a tall
picture with a partial last CTB row, 16-sample CTBs - and decodes
each four ways, one thread, four contexts in rotation, four pictures
concurrently, and concurrently with every row's publication held
back, every way against ffmpeg's frames. Transquant bypass the
decoder refuses, out of its profile; the oracle holds it to
refusing whole rather than decoding wrong.

It found the row hook filtering a CTB row the moment it was
reconstructed, when the next row's intra prediction reads this row's
last line as reconstructed and unfiltered. The hook filters the row
above now, once nothing will predict from it, and SAO runs two rows
behind; a row is final below what SAO has been over. Both preview
fixtures were wavefront-coded, where the hook never runs, which is
how the hashes stayed equal: a non-wavefront fixture joins them, and
the preview oracles run it plain and with rows held back. The CI
sample runner takes the new oracle.
2026-09-22 01:41:19 +02:00
LibretroAdmin 5cc7019d05 video: the threaded wrapper's size pairs travel as one word
The viewport and video-mode commands, the menu texture and the frame
slot each carry their size as one packed word, so a frame's size
reaches the video thread in one store and is read back in one load.

The packet union's unused 'output' member goes with them.
2026-09-22 01:34:55 +02:00
libretroadmin 494039408e rh264_lossless_test: the reference is the stream's pictures, no more
The field file that found the multi-slice fault kept a residue of
1,166,172 differing samples at one thread, all in the last picture.
It was the reference: ffmpeg duplicates pictures to hold a file's
declared rate against its timestamps, so a file of 127 packets gave
129 frames, and the decoder's last picture - correct to the byte -
was compared against a copy of the one before it. Every reference
the oracle makes now takes -fps_mode passthrough, and the file mode
makes its own reference that way from the file alone, RH264_REF
naming one made already; its exit status is the comparison. The
field file decodes byte-exact at one thread, four contexts and four
threads.
2026-09-22 01:26:37 +02:00
libretroadmin 4b5a76b875 rh265: pictures decode concurrently on the pool
The fourth step, and the arc's point: with a pool and more than one
thread, HEVC pictures decode concurrently. A picture's slices are
queued on its context as they arrive - each job takes the unescape
buffer it was parsed from, the decoder draws another from a pool, and
the slice header and lists go with it - and go to the pool when the
next picture opens, at which moment the picture's place in the
sequence is settled, out of the decoding slot and into the output
order, with its rows still counting up. A reference read sleeps for
the rows it reaches and no more; a picture holds the slots it reads
so the DPB hands none of them to a new picture; the output holds a
picture until it is complete and waits on its last row only when the
queue is full; the drain and the close land everything first. The
wavefront stays off while pictures are on the pool - the pool is the
pictures'. The delay knob holds every row's publication back at
random, so that the readers wait rather than nearly always find their
rows, and the output must not change under it.

Three things the sanitizers found on the way, all outside the
scheduler: the pool entry and the close changed state under pictures
in flight, and join first now; the catch-up flag crossed from the
caller's thread to the decode's as a plain int in both decoders, and
is atomic in both; and the preview's resident-bytes diagnostic read
the window's bookkeeping while the feeder moved it, and reads a value
the feeder publishes instead.

Byte-exact against one thread on both HEVC fixtures at two and four
threads, plain and with rows held back, under ASan and TSan; the
threaded preview harness at four threads, both codecs, under both.
The check target and CI run the concurrent and the held-back lanes.
2026-09-22 01:18:59 +02:00
LibretroAdmin dd877f674e gfx: the display, widget and thumbnail size pairs travel as one word
struct gfx_display carries its framebuffer size, the gathered batch's
first rectangle and the batch's video size as three packed words;
dispgfx_widget_t its last video size and the AI service overlay's; and
gfx_thumbnail_t its own. The batch compare that decides whether a quad
can join asks one word per pair, and a thumbnail takes its size from
the surface as a single copy.

The AI overlay's loader keeps two locals for the decoder's out-params
and packs the answer, since the decoder fills them separately.
2026-09-22 01:18:45 +02:00
libretroadmin 123026d622 rh265: pictures take decode contexts in rotation, and the WPP oracle decodes at four
The decode state - the parameter sets and slice header the picture
decodes with, its reference lists, its maps of intra modes, coded
depths, edge strengths, QPs and SAO parameters, its scratch, its
SAO bands - was one struct in the decoder; it is one per context now,
eight at most, and a new picture takes the next context round, which
readies its own maps for the decoder's geometry while the DPB is
remade only when the geometry changes. With one context that is the
decoder as it was. The pictures still decode one after the other, so
rotating changes which memory a picture uses and nothing else: the
WPP fixture and the B-frame fixture hash the same at four contexts as
at one, and the WPP oracle now decodes at four - a difference would
be a piece of picture state still in the decoder rather than the
context, shared between pictures the moment they decode concurrently.
This is the third of H.264's four steps; the scheduler is what
remains.
2026-09-22 00:56:25 +02:00
LibretroAdmin 8bd1e965a9 video: the size pack takes each axis as unsigned
VIDEO_SCALE_PACK converts an axis before it compares it against the
limit, so a caller holding its sizes in int or float packs without a
sign-compare warning and without a cast of its own. The clamp is
unchanged: at the limit, past it, negative and fractional inputs all
land where they did.
2026-09-22 00:45:56 +02:00
libretroadmin 5966b671d9 rh265: a picture counts the CTB rows that are final, and every reference read asks
Each picture in the DPB counts the CTB rows of it that are final -
reconstructed, deblocked, SAO'd, written by nothing again - counted
up by the row hook as the filters pass and set to the whole picture
at the finish. What is final is what SAO has been over, since the
row above's SAO reads this row's first line and the edge at the top
of the row below still touches this row's last; without SAO, what is
deblocked below the row still being filtered against. Every read of
a reference asks for the row it reaches: the motion compensation for
the block's last line displaced by its vector and the eight-tap
filter's four lines past it, temporal prediction for the co-located
row. On one thread a reference is complete before it is read and the
ask is answered at once; a picture on another thread will wait
there, and nowhere else. The WPP oracle asserts that no read on one
thread or on the wavefront finds its rows short, the bench reports
the count beside the hash, and both fixtures hash as before.
2026-09-22 00:45:36 +02:00
libretroadmin f2beaaca54 rh265: the loop filters run a CTB row behind the decode
The deblocking filter ran over the whole picture at the finish, and
SAO after it. Both go a CTB row at a time now: when a row is
reconstructed its edges are filtered - vertical, then horizontal from
its top edge down, which reads only lines the band above has already
filtered vertically and reaches no line the band below has yet to -
and SAO runs over the row above, whose last line reads this row's
first as deblocked. Done band by band from the top that is the
picture's filter to the sample; the WPP fixture and the B-frame
fixture hash the same as before at every thread count. The finish
does what the hook left: the last row's SAO, or the whole picture
when the hook could not run - the wavefront, or more than one slice.
This is the first of the four steps H.264 took towards pictures
decoding concurrently: rows that become final one at a time, for a
later picture to wait on.
2026-09-22 00:36:29 +02:00
LibretroAdmin 89b917eb82 osmesa_ctx: the driver declares the types it uses
The headless OpenGL context includes the headers that declare
gfx_ctx_driver_t, gfx_ctx_proc_t and input_driver_t, so a build that
configure enables from its pkg-config probe compiles. bind() takes its
address as a struct sockaddr, and the unused second sockaddr goes.

compile-matrix gains eleven context-driver lanes, osmesa among them:
these answer the frontend's window and size questions and no other job
here compiles them, so each lane names the headers it needs and says so
where the runner has none. vivante_fbdev stays out, since only that
vendor's EGL headers declare the functions it calls.
2026-09-22 00:29:53 +02:00
libretroadmin 6b83f9407a rh264: a sample's slices are all fed when pictures are in flight
A file whose every picture is coded as eight slices rendered at one
thread and not at two or more. rh264_video_decode fed a sample's NALs
until a picture was shown and stopped - one coded picture per call,
which sequentially is exact: the picture shown is this sample's,
complete at its last slice. With pictures in flight the picture shown
at a slice's open is an earlier one, and stopping there left the
sample's remaining slices unfed: rows of the first slice decoded and
the rest never, the picture never complete, every picture predicting
from it waiting for rows that could not come. The call now ends with
the sample. Two more that the same file found: a continuation slice
was refused because its start was checked against a picture end the
job had not yet reached - the slices must arrive in raster order at
submit, and the job checks each against the last one's end when it
runs them; and a picture that refused published no rows on the
inline path, which was the hang. Every picture is published whole
once its run is over, however it ended.

The oracle gains an eight-slice case with B-frames, decoded in all
four modes, and a mode that decodes one file against its ffmpeg
reference at one thread and at N - the file from the field, and the
next one. The preview sample's threaded target was listed in all:
before its variable was set, so make read an empty prerequisite and
CI found no binary; the definitions come first now.
2026-09-22 00:29:32 +02:00
LibretroAdmin 58a1a327c8 samples/audio: sweep every DSP filter with adversarial presets
The lane for the two preceding fixes. It asks each filter which
parameters it reads, then re-inits it once per parameter per poison
value - zero, negative, huge, infinity, NaN, and the int edges - across
five sample rates including zero, and runs audio through both the float
and the int16 path. Discovery is by callback, so a parameter a filter
gains later is covered without touching the harness.

Each filter is swept in its own process, so one that dies still leaves
the other fourteen reported, and an alarm turns a loop the preset sizes
into a named failure rather than a hang. Built with
-fno-sanitize-recover, a sanitizer report is a trap, which the parent
reports the same way.

Run it as: make SANITIZER=address,undefined,float-cast-overflow check

Not wired into .github/workflows/Linux-samples-audio.yml, which names
its directories one at a time.
2026-09-22 00:28:16 +02:00
LibretroAdmin b3d5060d40 dsp_filters: bound the rest of the preset parameters
The same class as the vibrato and tremolo fix, across the remaining
filters: a preset value reaches a Q16/Q24/Q30 conversion, a divisor, an
allocation size or a loop bound without one.

Clamp each parameter at init to the range its buffers and fixed-point
format hold, with the NaN-safe ordering. The bounds that are not simply
the control's own range: chorus takes the modulated delay against the
ring, since the int16 LUT carries it as Q16 samples; chorus lfo_freq
sets the LUT length and the loop that fills it; eq block_size_log2 is a
shift count, an FFT order, an arena size and a block divisor at once;
iir quality divides the biquad alpha, as wahwah resonance does.

Refuse a zero input rate, which is what a filter is handed when the
audio device never opened, and floor the reverb delay lines at one
frame - at a low enough rate every comb length rounds to zero and
comb_process reads past a one-byte arena.

Saturate the Q16 state in echo and reverb. Both let a preset set a loop
gain of exactly 1, and echo's amp sits inside the feedback path, so the
state grows until the int64 product overflows. The float paths go loud
instead; the ceiling is 2^20 times full scale, so a setting that decays
never reaches it and its output is unchanged.
2026-09-22 00:28:13 +02:00
LibretroAdmin 4d7d2d3141 dsp_filters: bound the preset parameters that kill the filter
A .dsp preset is a text file the user picks, and rstrtof parses "inf"
and "nan", so every parameter reaching these filters can be zero,
negative, enormous or non-finite.

Vibrato divides by an LFO period it re-derives per sample from freq
without the clamp vibratocore_carve applies, so a freq above the sample
rate truncates it to zero and the modulo raises SIGFPE; take the clamped
core->maxphase instead, which also drops a division from the per-sample
path. Tremolo takes its wavetable index modulo a length derived the same
way, with the same result on both the float and the int16 path.

Overdrive, early reflection reverb and WSOLA clamp their parameters, but
the clamp returns NaN unchanged because NaN compares false against both
bounds. In overdrive that reaches the Q20 bias, and the tanh LUT is then
indexed out of its 4097 entries - a segfault. Order the comparison so a
NaN lands on the low bound.

Clamp vibrato and tremolo's freq and depth to the ranges their buffers
are sized for, floor the vibrato delay line at the four samples the
Hermite tap reads, and refuse a zero input rate, which is what the
filter is handed when the audio device never opened.
2026-09-22 00:28:09 +02:00
LibretroAdmin fed510bc42 xmb: a shrunken icon centres on its own height
The VITA, Wii U and PS3 centring path offsets y by the icon's height,
so a non-square icon drawn with a scale factor below 1 sits centred on
both axes.
2026-09-22 00:16:39 +02:00
LibretroAdmin 69d23129c2 xmb: the console scale goes into the packed size
The VITA, Wii U and PS3 path takes its scale in the icon size, so the
size is packed already scaled rather than multiplied afterwards.

compile-matrix gains five lanes that compile the shared menu and video
files under the console platform macros, against the stubs already in
the tree. The blocks those macros select are not compiled by any
desktop job, so a change to a struct they touch stays green until a
console job runs; these fail by name instead.
2026-09-22 00:08:01 +02:00
LibretroAdmin 0124aab168 audio_mixer: bound the streaming voice's resampled buffer
A streaming voice sized its resampler output buffer as
AUDIO_MIXER_TEMP_BUFFER * ratio plus a fixed 16 samples, then stored
whatever process() reported into it. The pad does not scale, but the
overshoot does: a call ends on a phase it did not start on, so roughly
one input frame's worth of output lands on top of the estimate, and
that is ratio samples, not 16.

Measured with the mixer's own constants and the sinc driver: at ratio 6
(8 kHz source into a 48 kHz mixer) the buffer holds 49168 samples and
process() reports 49166 - two to spare. At ratio 12 it holds 98320 and
process() reports 98330, and at ratio 24 it is short by 34. Both are
ordinary sources; the buffer is on the heap, so the excess lands in
whatever follows it.

buf_samples was written at both allocation sites and never read - the
bound was recorded and not enforced.

Scale the pad with the ratio, as one_shot_resample() already does for
the non-streaming path, and clamp the reported count to buf_samples at
both fill sites, so a backend that reports more than the estimate
truncates rather than overruns.

The audio_mixer_rate_guard harness gains a streaming lane driving
64 mix calls per voice at 44.1k, 8k, 4k, 3k and 2k sources into 48 kHz
and 96 kHz mixers, both sample formats. Reverting this change makes it
report a heap-buffer-overflow in resampler_sinc_process_sse_kaiser
under ASan.
2026-09-21 23:56:26 +02:00
LibretroAdmin 1291fee739 exynos: the driver's size pairs travel as one word
exynos_data carries the framebuffer size as dims, exynos_video the
core's current mode, and the image defaults table its own sizes, all in
VIDEO_SCALE_PACK's layout. The frame path compares one word to decide
whether the core changed resolution, and the scaling maths unpacks once
into locals.

compile-matrix gains an exynos lane against libdrm's exynos module and
its G2D headers, which nothing else in the tree compiles; it skips
where those headers are absent. platform_video's failure output now
goes through show_out, so a lane that fails leads with its error rather
than whatever warning came first.
2026-09-21 23:36:09 +02:00
libretroadmin b31b6c180b rh265: SAO reads from a band of one CTB row, not from a copy of the picture
The sample adaptive offset reads the deblocked samples around each one
and writes in place, and it did that from a copy of the whole picture
- three planes malloc'd, twelve megabytes at 4K, copied and freed for
every picture with SAO on. A band of one CTB row and a line above and
below, kept with the decoder, serves the same reads: the line above
comes from the previous band, where it is still as deblocked, the rest
from the picture ahead of the pass. The output is the same to the
hash on the WPP fixture, at every thread count. The preview bench
takes BENCH_HASH, a hash over every frame's pixels, which is how two
builds of a decoder are held to the same output on a file.
2026-09-21 23:27:47 +02:00
libretroadmin 9f3a581199 rh265: the wavefront's rows wait on their own count, not on the pool falling idle
The WPP rows went to the pool and the caller waited for the pool to
fall idle - a wait for everything on it, which is fine while a pool
carries one picture's rows and nothing else, and a stall on every
picture once pictures decode concurrently on the same pool, as the
H.264 decoder's do and this one's are to. The rows belong to a group
on the heap now: the runners claim them, the caller too, and the
caller waits on a process-wide eventcount for the rows in other hands
and then for the runners to let go - a runner still queued behind
other work finds its rows gone and leaves at once, but until it has,
the shadows and counters it can reach are not the next picture's to
reset; that was the race the first shape of this had. Byte-exact on
the WPP oracle across the thread counts, under ASan and TSan.
2026-09-21 23:21:47 +02:00
LibretroAdmin 744d9daad4 samples/gfx: the async lane asks the worker, not the clock
The lane's non-blocking check timed six posts against a fixed 5 ms
budget, which on a two-core WARP runner under gdb measures how often
the poster is descheduled rather than whether the post waits on the
video thread: it reports ~19 us here and failed CI at 5649 us.

The first load now parks the worker inside the upload, where it runs
with no lock held and with nothing on the main thread waiting on it.
The remaining five are posted into that, and the lane asserts they
came back while the worker was still in there - the worker's own
state answers, so the check holds however loaded the machine is. A
post made to wait on the video thread fails it with the hold's own
bound.
2026-09-21 23:21:21 +02:00
LibretroAdmin 0c7e6bbf9d gfx: a draw descriptor carries its size as one word
gfx_display_ctx_draw holds dims in VIDEO_SCALE_PACK's layout, so the
size reaches every display backend in one member. The producers pack
where they already had both axes in hand, and each backend unpacks
where it builds its viewport or its quad.

samples/gfx/gfx_widgets_sanitize gains an icon-draw lane: it draws at
64x16 and checks both halves of the word the widget layer hands the
driver, so a transposition on the way in fails there. The Direct3D
lanes in the compile matrix cover the six backends no other lane
compiles.
2026-09-21 23:12:12 +02:00
libretroadmin 29e24ae6a2 gfx_thumbnail: the preview's threads defer to a core that is running
The plan for threaded previews had the worker count defer to a
running core, and the setting was taken as it stood: a 4K preview at
four threads under a menu open over running content took the cores
the game was on. The count is the setting unless content is loaded
and not paused, in which case one - the preview decodes as it did
before the setting, and the pool is remade as the count changes,
which it already was.
2026-09-21 23:06:24 +02:00
LibretroAdmin 98d5a9ef57 (Resampler) sinc and nearest hold their ratio contract per call, not just at init
Both drivers validate the nominal ratio when the instance is made and
then take a different number on every call: audio_driver_effective_ratio()
multiplies by slow motion and fast-forward, and audio_driver_bound_ratio()
returns the ratio untouched wherever the caller reports no capacity of
its own. Nothing checked that one.

sinc derives its phase step as phases / data->ratio and nearest as
1.0 / data->ratio, so a ratio of zero, a negative one, a NaN or one
larger than a pair of sample rates can name leaves a step that never
advances the loop, and the inner loop emits past data_out until it
leaves the buffer. The integer drivers reach an int64 cast on the same
value, where the conversion is undefined.

Each dispatcher now holds the range its own arithmetic needs before it
reaches any of that. sinc uses sinc_resampler_ratio_valid(), the
predicate its init already refuses on - it classifies by bit pattern,
so it survives the float driver's fast-math mode - and nearest bounds
the step the same way the CC driver does.

resampler_reset drives all three backends through the ratios a clamp
would have caught, instead of CC alone with a note that the other two
were theirs to answer for. Reverting either fix turns that lane into an
ASan heap-buffer-overflow, in resampler_sinc_process_c_kaiser and in
resampler_nearest_process.
2026-09-21 22:57:57 +02:00
libretroadmin 350f7f7142 rh264: the slice jobs are kept between slices
Each slice's job was made with calloc and freed after - two kilobytes,
a B context inside, zeroed and returned per slice on the submitting
thread. A finished job goes to a pool on the decoder beside the
unescape buffers, and the next slice takes it from there, cleared;
its buffer is settled - pooled or freed - before the job is offered,
since a pooled job is another thread's to take. With this the per-
picture path on the submitting thread makes no allocation in steady
state: blocks, buffers and jobs all cycle.
2026-09-21 22:55:26 +02:00
libretroadmin e51169e0fd gfx_thumbnail_preview: the preview oracle runs threaded, on the app's own path
The preview harness built the thumbnail without HAVE_THREADS: the
decode ran inline on the poll, no worker, no pool, and the paced
stall check could say nothing about the app, where the picture
decodes on the worker and, above one thread, on the pool. The same
test now builds a second way, gfx_thumbnail_preview_test_threaded,
with rthreads, the pool and the eventcount real and the stubs' thread
shims compiled out; THREADS sets the count as the menu setting would.
The check target and the CI job run it at four threads, paced, with
a bound on the longest stall, under ASan and under TSan - the first
time the worker, the pool and the poll have been under a sanitizer
together. Clean.
2026-09-21 22:51:00 +02:00
libretroadmin 560dab6972 Animated previews: the feed's sums saturate rather than wrap
Three sums of file-derived sizes were unchecked: twice what the decoder
took since the last feed, the decoder's position plus the lookahead,
and the same for the audio window. On a normal file none can wrap; on
one that can, the first would budget a decoder that took the most next
to nothing and the others would clamp a wrapped bound against the
length and repair nothing. Each saturates.
2026-09-21 22:40:44 +02:00
LibretroAdmin 6bdbe17439 compile-matrix: the Direct3D drivers get a lane
Seven lanes compile d3d8, both d3d9 backends, d3d10, d3d11, d3d12 and
gdi against the DirectX headers the tree vendors in gfx/include/dxsdk,
through a mingw-w64 cross compiler. These drivers read the same frame
and draw descriptors every other video driver does, and a descriptor
field they are not carried along with fails all seven by name.

The cross compiler is the only thing needed beyond the tree; where
there is none the lanes say so and the matrix passes as before. A
runner installing gcc-mingw-w64-x86-64 turns them on.
2026-09-21 22:28:43 +02:00
libretroadmin fd368664ae rh264: a slice is unescaped once, into a buffer that is neither made nor copied per slice
The queued job copied the unescaped slice out of the context's scratch
into a buffer made for it - a megabyte moved and an allocation freed,
per slice, on the submitting thread, for bytes that already sat in
memory nobody else needed. The job takes the buffer the slice was
unescaped into - whichever context's was current then, the one before
the rotation when the slice opened a picture - and that context draws
its next from a pool of buffers on the decoder; a finished job returns
its buffer there. Workers return and the submitter draws, under the
blocks lock when there is a pool. Byte-exact in every mode, clean
under the sanitizers.
2026-09-21 22:27:30 +02:00
LibretroAdmin d1d064e24c gfx: a surface carries its size as one word
gfx_surface_t holds dims in VIDEO_SCALE_PACK's layout. The thumbnail
animation path compares one word to decide whether a surface still fits
the frames it is being handed, and the overlay and swizzle paths unpack
where they compute a frame length.
2026-09-21 22:18:59 +02:00
LibretroAdmin 1d19ba92c5 video: the softfilter output size is one out-param
rarch_softfilter_get_output_size() and get_max_output_size() hand the
size back as one word in VIDEO_SCALE_PACK's layout. The plugin ABI is
untouched: a filter still answers through two pointers, seeded from the
word so one with no size query of its own leaves it as the caller had
it.

video_driver_init_filter() carries the packed size from the geometry
through to the buffer allocation, and the frame path, the threaded
wrapper and the recording setup each hold one local for the pair.
samples/gfx/filter_lifecycle checks the output dimensions it gets back,
so a halved unpack fails there.
2026-09-21 22:18:55 +02:00
libretroadmin 9705e03a59 rh264: output slots hold no block of their own, and let a shown block go at once
Every output slot was allocated a sample block of its own - twelve
megabytes at 4K, zeroed - to have a frame picture's block shared over
it on the spot, and once a picture had been shown the slot kept the
block until it came round again, which with eighteen slots is a dozen
pictures later. So the free list stayed empty and every picture paid
a fresh block: a mapping from the kernel and its zeroing on the
submitting thread, per picture, and the memory of a dozen pictures
pinned in slots nobody read. A slot for a frame picture is now made
as a shell, bookkeeping only, and a slot that has been shown and is
not on show lets its block go the moment the next picture is pushed.
Field pairs, which copy, keep a block. The free list holds sixteen.
2026-09-21 22:13:01 +02:00
LibretroAdmin 0d7e6acb7f (CC resampler) Full precision builds, and builds alone
CC_RESAMPLER_PRECISION above 4 selects a trig kernel only the reference
implements. Two things followed from that and neither was intended.

It did not compile: cc_int() uses M_PI and sinf() at that setting while
the file included neither <math.h> nor retro_math.h, so the mode the
header calls full precision has been unbuildable under -std=c89. Both
includes are there now, and 0, 1 and 5 compile clean on x86 and armhf.

And since the arms became a runtime choice, building it would have left
one binary resampling differently depending on which arm the mask
picked - the reference on its trig kernel, the vector arms on the
polynomial they are the only implementation of. Above 4 the vector arms
are no longer built, so the setting means the same thing whatever the
mask says.

resampler_simd reads the same condition, so at that setting it reports
that there is no vector arm rather than comparing the reference against
itself.

What the setting buys, measured at 32 to 48 kHz: about 21 dB of THD+N
at 100 Hz, 8 dB at 500 Hz, 3 dB at 1 kHz, and nothing at all above
2 kHz, where the width of the kernel is the limit rather than the way
it is evaluated.
2026-09-21 22:12:40 +02:00
libretroadmin d4a7c4ba5a Animated previews: the feed's budget follows the decoder's appetite
The feed runs once per frame, on the decoding thread, and read half a
megabyte a tick. A decoder with pictures in flight takes its samples
in bursts of three and four a frame, more than that refills, so the
lookahead ran down each few seconds - and then the feed's answer was
to extend unpaced: two seconds of 4K read from disk in one go, on the
thread the frame was waiting on. A pause every few seconds, in a
preview that decodes faster than it plays.

The budget is now at least twice what the decoder took since the
last feed, so a decoder that takes four samples a frame is kept ahead
of without the lookahead ever running down; and a lookahead that has
run down is made up a quarter of the shortfall a tick rather than at
once. Only a decoder already past the frontier gets the unpaced
extend, since its next read would fault.
2026-09-21 21:58:16 +02:00
LibretroAdmin ffb120c83b samples/audio/extra_capacity: stub the config_userdata callbacks
4d5f87f added audio_resampler.c to this sample's link, and that unit's
resampler_config table points at config_userdata_get_float() and the
rest of the config_userdata family. Without a sanitizer --gc-sections
drops the table and nothing notices; under
SANITIZER=address,undefined it survives and the link fails with
undefined references, which is how the Linux audio samples job broke.

Stub them the way resampler_options_test does, aborting since nothing
here passes backend config userdata, and mark them externally_visible
so -fwhole-program on extra_capacity_test.o does not internalise
them.
2026-09-21 21:57:53 +02:00
LibretroAdmin ae58b972cb (Audio) The flush's zero-copy path does not ask for an aligned buffer
audio_driver_flush() points the resampler at the core's own buffer when
nothing needs to be produced into input_data, and fell back to the copy
unless that pointer was 16-byte aligned. The requirement was the CC
resampler's ARM assembly, which loaded its input with an explicit
alignment hint; that file is gone and nothing has taken its place.

Every consumer of src_data.data_in reads it without one: sinc pushes it
into its own ring a sample at a time, nearest copies frame structs, CC
takes it with _mm_loadl_pi and vld1_f32, and the bypass clamp uses
_mm_loadu_ps and vld1q_f32. So the check only decided whether a batch
was copied, and a core-owned buffer under an allocator that aligns to 8
paid for it every flush.

Worth about 1% of the resample where it fires - 512 stereo float frames
at 48/32, best of 9, 0.0862 s against 0.0871 s through CC and 0.1266 s
against 0.1283 s through sinc. Small here, and this is the host with the
fastest memcpy and the batch already in L1.
2026-09-21 21:57:53 +02:00
LibretroAdmin 15ac073eef extra_capacity: supply the int16 factory rather than linking the layer
The harness builds its test object with -fwhole-program and overrides
retro_resampler_realloc_hq(), so it has always supplied the resampler
layer rather than linking it. When retro_resampler_int16_new() arrived
the layer was added to its sources instead, which links
audio_resampler.c's driver table and with it the config_userdata_*
callbacks the harness does not define. The plain build survived because
-fwhole-program lets the linker drop the table; the sanitizer build did
not, so the suite could not be sanitized at all.

It supplies the factory too now, against the sinc int16 driver its lanes
already drive, and links neither resampler-layer unit. Both build modes
pass, ASan and UBSan among them.
2026-09-21 21:57:53 +02:00
LibretroAdmin 5149b27677 (CC resampler) A nominal ratio it cannot serve is refused at init
sinc_resampler_int16.h promises NULL when the nominal ratio cannot
safely advance the phase clock, and both sinc drivers enforce it before
they configure anything. CC took any ratio and only refused per call,
through the output bound, so a core reporting no rate gave a resampler
that created cleanly and then produced nothing, where sinc gives the
frontend a failed init and a log line.

Both CC drivers now hold the same range at init that process() holds:
positive, and no larger than a pair of sample rates can name. The
predicate is the one the output bound already used, named once.

resampler_options pins it for the float driver and stretch for the
integer one; without the check each reports three failures.
2026-09-21 21:57:53 +02:00
Gorilli09 bffc23a5aa Add explore-by-release-month option (#19602)
Introduce a new "release month" explore category. Adds EXPLORE_BY_RELEASEMONTH and its explore_by_info entry in menu/menu_explore.c, and the corresponding MENU_ENUM_LABEL_VALUE_EXPLORE_BY_RELEASE_MONTH in msg_hash.h to enable browsing by release month.
2026-09-21 21:49:37 +02:00
LibretroAdmin 5bb94c70a5 omap: both of the driver's size pairs travel as one word
omap_video_t carries the current mode as dims and omapfb_data_t the
native screen size as nat_dims, in VIDEO_SCALE_PACK's layout. The
frame path compares one word to decide whether the core changed
resolution, and the font, viewport and menu-texture paths unpack once
into locals.
2026-09-21 21:44:47 +02:00
LibretroAdmin 9c504d6852 video: the statistics overlay reads the cached size from one variable
The overlay's six references to the cached frame's dimensions unpack
from the word frame_cache_peek() hands back, so the block carries one
local for the pair.
2026-09-21 21:44:43 +02:00
libretroadmin cc1df72c75 rh264: the submitter helps the pool, eight contexts, and catching up leaves the pool its pictures
Two threads gave a picture in flight and nothing else: the pool had
one worker and the submitter sat idle at the join. The pool gains
tpool_help(), which runs the job at the head of the queue on the
calling thread, and the join takes queued pictures that way before it
sleeps - the one it waits for, or an earlier one the workers have not
reached; the head is taken as a worker takes it, so a picture never
waits on one still queued. The contexts number the threads plus one,
up to eight, so the pictures in flight are the threads that can
decode them and one being built.

Catching up dropped the pictures nothing references. Decoding
concurrently those cost nothing - they run beside the chain of
references, the critical path either way - so dropping them bought no
time and emptied the pool: a preview that fell behind stayed behind,
on the pictures it had left, in fits. Threaded, the drops are
declined; on one thread they are what they were - except that a
dropped picture now says so, which lets the container pass its slot
on the timeline: the flag was never set here, so every H.264 preview
that caught up read its frame durations one slot behind.
2026-09-21 21:40:27 +02:00
libretroadmin f15022a18f audio_transfer: the AAC loop withdraws the window bound on the rewind it actually takes
The mixer's repeat rewinds an AAC stream through audio_transfer_seek's
own frame-zero path, not through the seek-to routine, and only the
latter withdrew the window bound on a rewind. So a looping preview
still read the head at once - the faulting instruction is the first
byte of the head packet, inside its declared size, on a page the
window gave up a lap ago. Both rewinds withdraw the bound now, through
one helper, and the reads stand at the wall until the feeder brings
the head back and publishes a bound for the new lap.
2026-09-21 21:25:26 +02:00
libretroadmin e16a4855d5 Animated previews: every windowed audio stream decodes under a bound
The bound the feeder publishes is what lets the decoder stand at the
wall on a loop instead of reading the head out of pages the window
gave up - and it was published at open only for the island layout.
For every other file the decoder trusted the demuxer's offsets
outright the whole lap, and at the lap's end looped and read the
head before the feeder's next tick brought it back: the access
violation in the AAC decoder, in the faulting instruction's own
terms, a byte inside the packet's declared size on a page that is
not there. The bound is published for every windowed stream from
its first read.
2026-09-21 21:19:54 +02:00
LibretroAdmin aab19d1a06 video: the cached frame's size pair stays packed end to end
frame_cache_snapshot(), peek() and store() hand the pair across as the
one word it is stored as, so the seqlock body is a straight assignment
and the four consumers that need separate axes unpack once, at the
point of use. The two that only forward the pair and the one that reads
only the pointer keep it packed.

video_driver_cached_frame_publish() compares one word where it compared
two, and the lock-based branch holds one field where it held two.
samples/gfx/threaded_video's size-pair round-trip lane covers this
path: a halved unpack anywhere along it fails the lane by name.
2026-09-21 21:19:01 +02:00
libretroadmin 860de3d0b3 Animated previews: the looping audio stands at the wall until the head is back
The preview's audio decodes on the audio thread out of a sliding
window the menu thread feeds, and the feeder lets the head go as the
lap goes on. When the audio reaches the end it loops, on its own
thread, and reads the head at once - pages the feeder gave back long
ago, and can only bring back on its next tick. That read is the
access violation in the AAC decoder when a preview reaches its end.

On a loop the decoder now stands at the wall: the bound the feeder
published for the old lap is withdrawn, so every read waits, and the
feeder, seeing the position fall back, forgets the bound it published
too, brings the head back and publishes one for the new lap. The
decoder resumes on it. A whole-file buffer has no window and no bound,
and loops as it did.
2026-09-21 21:08:33 +02:00
LibretroAdmin dd3f51b56b CI: companion_thumbs_test links retro_eventcount.c
dc98c1a moved image_blit_bands onto retro_eventcount, but
tools/companion_thumbs_test.sh links image_blit_bands.c without
retro_eventcount.c, so the link fails with undefined references to
retro_eventcount_init / notify / prepare_wait / commit_wait /
cancel_wait. Add it to the script's source list.
2026-09-21 21:05:43 +02:00
LibretroAdmin 617a1c13df (CC resampler) The kernels are chosen by the mask, and NEON is one of them
CC picked its kernel with a preprocessor chain: SSE under __SSE__, the
external NEON assembly under HAVE_ARM_NEON_ASM_OPTIMIZATIONS, the C
reference otherwise. That macro is defined nowhere, so every ARM build
took the scalar path while sinc - which keeps a NEON intrinsics kernel
beside its assembly one and dispatches on resampler_simd_mask_t - was
vectorised on the same hardware.

Every arm is now built and init() picks the pair the mask names, as
sinc does. The kernels are NEON intrinsics rather than assembly:
vzipq_f32 gives the lane spreads _mm_shuffle_ps built and vcombine the
half splices, so the two arms are the same arithmetic in the same
order. Multiply and add are kept separate rather than vmlaq_f32, which
would round once where the other arms round twice.

cc_resampler_neon.S goes with it. It addressed struct offsets that no
longer held, read its ratio with vldr s4, [sp] under a hard-float
platform, and had no output bound; it is in the history if the
alignment hints are ever wanted back.

samples/audio/resampler_simd drives every arm a build has over the same
stream - upsampling, downsampling, unity, rate control's excursions -
against the scalar reference, and skips an arm this build lacks rather
than comparing the reference against itself. The arms agree to one
ulp, not to the bit: cc_int()'s polynomial has double literals, so the
reference rounds through double where the vector arms stay in float.
Swapping the two NEON lane spreads moves the worst difference from
1.19e-07 to 1.05.

Built armhf with -mfpu=neon and run under qemu-arm: same frame counts
as the reference, same one-ulp agreement. Measured on x86, SSE is 4.84x
the scalar arm upsampling and 2.98x downsampling; the qemu figures say
nothing about speed and are not offered as such.

No AVX arm. One was written and measured: correct to the same ulp, and
6% slower than SSE at 48/32. The kernel is four taps wide, so a 256-bit
multiply saves one instruction and costs three - an insert to spread the
weights, an extract and an add to fold the halves. sinc gains from AVX
because it streams 128 taps and more.
2026-09-21 21:05:13 +02:00
LibretroAdmin 69dcae7499 video, record: one packing for every size pair
The width/height pairs that travel as a single word all use
VIDEO_SCALE_PACK and its accessors now: the threaded wrapper's
viewport slots, the cached frame's dimensions, the output size, and
record_video_data, which carries both axes as one member. The macros
move to video_defines.h so the record drivers reach them. Clamping
applies everywhere the macro does, so an axis past 65535 saturates
rather than wrapping into a small number.

samples/gfx/threaded_video gains a size-pair round-trip lane: it
publishes at dimensions no square frame can hide, reads both axes back
through the public accessors, and checks the clamp at the top of the
range. A store and a load that disagree on which half holds which axis
fails it by name.
2026-09-21 20:46:04 +02:00
LibretroAdmin 63cc48514a CI: run tpool_parallel_test in the libretro-common samples job
5f19526 added samples/rthreads/tpool_parallel_test, but the samples
workflow fails any built target that is in neither RUN_TARGETS nor
NORUN_TARGETS, so the job has failed since. The sample is a
self-contained pass/fail test that runs in about 1.2 s and passes on a
one-core machine, so it goes in RUN_TARGETS beside tpool_wait_test.
2026-09-21 20:44:03 +02:00
libretroadmin dc98c1a6ac image_blit_bands: the bands are claimed, the caller helps, and nobody waits on the pool
The row bands of a blit went to the pool and the caller waited for
the pool to fall idle - which, once the pool also carries pictures
decoding for the frames to come, is a wait for every one of those on
every frame. A sixteen-core run of the concurrent H.264 decoder had
three pictures in flight and one core's worth of decoding, and this
was why.

No band belongs to a runner now. Whoever runs - the caller included -
claims the next band from an atomic index and does it until none are
left; a runner the pool starts late, behind a picture, finds them all
claimed and leaves. The caller never sleeps while there is a band it
could do itself, and waits only for the last few in other hands, on a
process-wide eventcount: no lock, no allocation but the group's, and
a syscall only when there is nothing left to do. The group is on the
heap and the last holder frees it, so a late runner has something to
find. Byte-exact on the blit-bands oracle under ASan and TSan.
2026-09-21 20:39:56 +02:00
LibretroAdmin 4d5f87fdbe (Resampler) One place builds an int16 backend, and the mixer uses it
The integer counterpart of a float backend was selected by an
if-chain on short_ident, written out three times in audio_driver.c -
the front pair, the extra-channel pairs and the microphone - and a
fourth site did not select at all: audio_mixer.c called
sinc_resampler_int16_init() outright, for both the one-shot WAV
conversion and the streaming s16 voice. So with CC or nearest chosen,
the game stream went through the selected backend while menu sounds
and BGM went through sinc, and those voices honoured Resampler Quality
while the stream did not.

retro_resampler_int16_new() is that selection, once, in
libretro-common. It resolves the name through the same lookup
retro_resampler_realloc() uses, so NULL or an unknown name lands on the
fallback, and it hands back the instance with its process, reset and
free - zeroed when the backend has no integer implementation, which is
the caller's signal to keep the float path. The quality mapping onto
sinc's own enum, which audio_driver.c and audio_mixer.c each carried a
copy of, lives there too.

It goes in its own unit rather than audio_resampler.c: a target that
never asks for an integer backend links none of the three int16
drivers, which putting it beside the driver table would have forced on
every consumer of audio_resampler.o.

audio_mixer_play_s16() takes the resampler_ident its float twin has
always taken, and the s16 voice carries the whole handle rather than a
bare pointer with sinc's functions assumed around it.

The HQ policy now reads RESAMPLER_CAP_HQ_OVERSAMPLE rather than
comparing the backend against &sinc_resampler, so extra_capacity's
stand-in for "another backend" clears that bit instead of relying on
its address.
2026-09-21 20:37:08 +02:00
LibretroAdmin 4820970267 (Resampler) A backend says what it reads, and the menu offers only that
Resampler Quality and HQ Oversampling were shown whenever the resampler
was not "null" (menu_displaylist.c:9149), while only sinc reads either.
With CC or nearest selected both rows were live controls that changed
nothing, and Quality cost a CMD_EVENT_AUDIO_REINIT to do it. On PSP,
where CC is the default driver, config.def.h even defaults Quality to
RESAMPLER_QUALITY_LOWEST.

retro_resampler_t gains caps, a RESAMPLER_CAP_* mask, last so a backend
that leaves it zero offers no control. sinc sets both, nearest and CC
zero. audio_resampler_driver_caps() answers for a name through the same
lookup realloc uses, so an unknown name reports the fallback's. The two
audio-page rows and the microphone page's quality row are shown against
it, and the HQ policy in audio_driver_resampler_realloc() asks the mask
rather than comparing the backend against &sinc_resampler.

resampler_options pins the mask per backend and through the name lookup,
including the fallback.
2026-09-21 20:37:04 +02:00
LibretroAdmin d6cd0395c8 (CC resampler) resampler_options drives CC, and three dead build bits go
The ident-lookup suite built only sinc and nearest, so nothing checked
that "cc" and "CC" reach CC_resampler through find_resampler_driver(),
or that the HQ request and the quality tier - neither of which CC reads
- still give one stream. It links CC now and covers both.

The suite freed its handle at the end of the nearest block and left the
pointer, which the next realloc would have freed again; the new block
clears it first.

Dead with it: CC_RESAMPLER_IDENT, defined three times and referenced
nowhere; HAVE_CC_RESAMPLER in Makefile.griffin, a make variable the
file never reads, since each platform passes -DHAVE_CC_RESAMPLER in
PLATCFLAGS; and cc_resampler_neon.o on Vita, which is built and linked
there although Vita defines neither HAVE_CC_RESAMPLER nor takes CC in
griffin.c, under PLATCFLAGS carrying -mfloat-abi=hard - the ABI those
kernels' vldr s4, [sp] is wrong for.
2026-09-21 20:37:00 +02:00
libretroadmin 1ab50b9d16 rthreads: the pthreads-on-Win32 timed wait reads the clock into the right struct
_ftime64_s fills a __timeb64 - seconds and milliseconds - and the
timed wait handed it the timespec it was about to give
pthread_cond_timedwait, which did not compile under a recent MinGW
and would have read garbage under an older one. The clock goes into
its own struct and is converted.
2026-09-21 20:14:23 +02:00
libretroadmin ad974bc082 samples/rthreads: the pool samples link without librt on Windows
MinGW has no librt, so -lrt stops both pool samples at the link;
glibc is the one that ever needed it, and only its older releases.
2026-09-21 20:07:56 +02:00
libretroadmin 5f19526311 rh264: the bench says how many pictures the pool decoded at once, and a pool oracle
A sixteen-core run had the pool's CPU time equal to the wall time:
however the pictures were posted, they decoded one at a time. The
decoder counts the most pictures its pool had decoding at one moment,
and the bench prints it beside the row waits. And rthreads gets a
sample that settles the pool by itself: four jobs each burning a spell
of CPU, posted to a pool of four, must run together and finish in
about one spell; jobs that never overlap fail it, and a machine that
gives the process one core is told apart from a pool that does not
wake its workers.
2026-09-21 19:57:30 +02:00
LibretroAdmin 44fb0e27f2 video, record: the remaining size pairs travel as one word
Four variables become two, and each site that used both now reads one.

The content scale the viewport maths produces was two fields that the
threaded wrapper then packed by hand into scale_packed; it is one field
in that same layout, so the wrapper publishes it with a copy and only
one place knows the packing.

The recording state's two pairs go the same way. A resize check that
compared width and compared height is one comparison against a freshly
packed viewport; the GPU size drops four size_t-to-unsigned casts with
the field; and --size's override is one test for "was it given" rather
than two. That override is now clamped at 65535 an axis by the pack,
which is past any size a recorder here opens.

video_frame_info_t keeps its own scale pair: it feeds the float
arithmetic the shader chain wants, where a pack would only be unpacked
again.

Verified: make check on plain, address+undefined and thread builds;
compile-matrix 112/0; C89_BUILD from a clean tree; hot-function-budget;
the tree builds release and C89 clean.
2026-09-21 19:39:25 +02:00
LibretroAdmin 9cb39b0af5 Threaded video: the published viewport travels as two words
Width and height go in one slot and the full size in another, width in
the high half, the layout scale_packed in the same struct already uses.
Four slots instead of six: two stores a publish, two loads a read, and
each pair is whole before the sequence is even checked.

Verified: make check on plain, address+undefined and thread builds; the
forced-atomic-backend loop over C11/GCC_NEW/SYNC/PS2/VOLATILE;
compile-matrix 112/0; C89_BUILD from a clean tree; hot-function-budget.
2026-09-21 19:39:21 +02:00
LibretroAdmin 978e8e0589 compile-matrix: two more files no job compiles, and errors first
The S3 cloud-sync backend only griffin includes, and nothing defines
HAVE_S3 for it at all; the Lakka wifi driver needs HAVE_LAKKA. Neither
is in the C89 build's object list, so neither was checked, and both had
a mixed declaration - s3_parse_url declaring a size_t after a log call,
connmanctl_tether_start_stop an int after a popen.

A failing lane also used to print the first twelve lines of the
compiler's output, which for a file with a page of warnings ahead of
its one error showed nothing to act on. The reporting moves into one
helper the four call sites share, and it prints the errors when there
are any.

Verified: 112 lanes, 0 failures, from a clean tree; C89_BUILD clean;
putting either declaration back fails its own lane.
2026-09-21 19:39:18 +02:00
libretroadmin b8860185ca rh264: the bench says where a picture in flight spends its time
The pipeline counters showed a sixteen-core run with one picture in
flight and nothing blocking the submitter, which leaves the pictures
serialising on each other. Two more numbers say whether that is so:
the reference reads that had to wait for rows, and how many rows short
they stood when they did, process-wide and reset on reading. And the
bench splits the CPU time of a run between the calling thread and the
pool (CPU_SPLIT=1), so a submitter busy the whole time is told apart
from one that only waits.
2026-09-21 19:38:48 +02:00
libretroadmin e11fadc1cb gfx_thumbnail_preview: clean removes the bench and the catch-up oracle too
The two newest binaries were left behind by clean, the .exe forms
along with them, so a rebuild after a pull could report the bench up
to date against sources it had never seen.
2026-09-21 19:22:03 +02:00
libretroadmin dfa4e68ced rh264: the pipeline says what it did, and the bench prints it
A four-core run of the concurrent decoder showed no gain, and nothing
in the tree could say why. The decoder counts what its pipeline does:
pictures posted to the pool, the pictures in flight at each post,
posts that found the most possible in flight, joins that had to wait
for a context, output pops that held a due picture for being
incomplete, and pops that waited for one with the queue full.
rh264_video_stats() reads them, the container and the image layer hand
the decoder out for a bench to ask, and the preview bench prints the
line under each thread count. Those six numbers tell a submitter
blocking on a context from pictures serialising on their reference
waits, which is the question a flat bench leaves open.
2026-09-21 19:15:14 +02:00
LibretroAdmin e410bb1cba (CC resampler) The NEON kernels guard on the macro that selects them
cc_resampler_neon.S compiled on __ARM_NEON__ alone, while the C file
selects the kernels on __ARM_NEON__ && HAVE_ARM_NEON_ASM_OPTIMIZATIONS
and every sibling .S - sinc_resampler_neon.S, float_to_s16_neon.S -
guards on both. Nothing in the tree defines that macro, so every ARM
build took the C reference and carried two exported functions nobody
called. It also left the reverse open: the macro defined, the file
assembled without the flags the kernels need.

Checked with clang -target armv7-linux-gnueabi -mfpu=neon
-mfloat-abi=softfp: no symbols without the macro, the two kernels with
it, and the file assembles cleanly either way.

Reviving the path needs more than this. The kernels take their ratio
with vldr s4, [sp], which is the soft-float convention and wrong under
-mfloat-abi=hard, and the upsampler has no output bound, so they should
not be switched on without a device to test them on.
2026-09-21 19:10:10 +02:00
LibretroAdmin a31f03b33a (CC resampler) The stream's state leads the struct
resampler_CC_init() allocates with memalign_alloc(32, ...), but the
function pointer sat first, so buffer landed at offset 8 on a 64-bit
host and 4 on a 32-bit one - never 16-byte aligned, and the SSE arms
had to take it with _mm_loadu_ps/_mm_storeu_ps for a state the
allocation had gone out of its way to align.

It is also the layout the NEON kernels address: cc_resampler_neon.S
documents "[r2+0] --> buffer, [r2+32] --> distance" and acts on it at
:84, :103, :187 and :188. With the pointer leading, the 32-byte store
at :187 writes buffer[0].l over re->process and distance is read and
written at buffer[3].r. Nothing compiles that path today, so this has
been latent rather than broken, but the offsets are now the ones it
was written against: buffer at 0, distance at 32, on both widths.

The SSE accesses become _mm_load_ps/_mm_store_ps. init() is the only
place the struct is allocated, so the alignment holds. No measured
speed change on x86 - best-of-7 over 20000 calls sits in the same
0.174-0.177 s band either way, which is what movups on aligned data
costs on anything recent; the older in-order targets this driver
exists for are where it is not free.
2026-09-21 19:10:07 +02:00
LibretroAdmin cd848c0539 (CC resampler) The int16 arm takes the float arm's two guards
cc_resampler_int16.c mirrors the float driver, including both of the
things just fixed in it.

The upsample loop had no output bound, and its phase step is quantized:
ratio = (1/data->ratio) * 2^24 is zero once data->ratio passes about
3.4e7, so re->distance never advances and the loop writes past data_out
forever. A negative ratio walks the other way, and a ratio that is not a
number reaches the int64 casts, where the conversion is undefined. Both
arms now refuse a ratio outside (0, 65536] before those casts, and the
upsampler stops at what the ratio asks for.

The direction is also latched at init from the nominal ratio while the
runtime one is multiplied by slow motion, and cc_i16_downsample() emits
at most one frame per input frame. Same move as the float driver: above
a ratio of one the stream goes to the upsampler and stays.

samples/audio/stretch gains both lanes for CC, float and int16 - eight
calls at 2.0 on an instance initialised at 0.5, and the five ratios no
pair of sample rates can name, into an exact heap buffer. Without this
commit the int16 half segfaults. sinc and nearest do run past data_out
on those ratios; the suite says so where it drives only CC.
2026-09-21 19:10:04 +02:00
LibretroAdmin 63a70f15c6 (CC resampler) A ratio above one moves the stream to the arm that serves it
resampler_CC_init() picks the direction once, from the nominal ratio
the frontend passes as bandwidth_mod. The ratio the backend is then
handed is a different number: audio_driver_effective_ratio() multiplies
it by the slowmotion setting, 3.0 by default.

resampler_CC_downsample() emits at most one frame per input frame - the
store sits inside the once-per-input body - so above a ratio of one it
cannot produce what it is asked for. Measured over 2,048,000 input
frames, an instance initialised at 0.5 and run at 2.0 returned 2,047,999
frames where 4,096,000 were due: half the audio, gone, for any core and
device whose nominal ratio is under 0.75 the moment slow motion starts.

So the stream moves to the upsampler, which serves either direction, and
stays there. The two carry distance differently, so the move restarts
the stream; it happens once. Measured at 48000 to 32000, the two arms
agree to two decimals on gain and THD+N at every frequency from 100 Hz
to 14 kHz, so nothing is given up by staying.

resampler_reset gains the lane: eight calls at 2.0 on an instance
initialised at 0.5. Without this commit it reports 7999 frames of 16000.
2026-09-21 19:10:00 +02:00
LibretroAdmin b5ee334623 fill_pathname_test: cover fill_pathname_join truncation
The function reports the length it wanted, so >= len is how a caller
detects a join that did not fit; callers sizing a path buffer from a
bound held elsewhere depend on that and on nothing outside the buffer
being written. Neither was covered.

Checked against an injected defect that clamps the return to len - 1:
the case fails there and passes once reverted. ASan clean.
2026-09-21 18:56:09 +02:00
LibretroAdmin 62c402acc6 vfs_implementation: use retro_posix_source.h
The inline feature block predates the shared header by one commit and
does the same job, leaving two idioms in the tree for it. The object is
byte-identical under the default profile.
2026-09-21 18:56:05 +02:00
LibretroAdmin 40334507dc psp_joypad: include the SDK header it takes its controller API from
The PSP path uses SceCtrlData, the PSP_CTRL_* bits and three sceCtrl
calls and includes none of them. It compiles because griffin includes
psp_input.c, which does include <pspctrl.h>, ahead of it - the VITA
path in this same file includes <psp2/ctrl.h> for itself, so the
omission is one-sided.

A compile-only pspctrl.h joins the seven psp stubs already there, and
two lanes follow: psp_input and psp_joypad, neither compiled anywhere
else. SceCtrlData carries what pspsdk's does in the same order and no
more - the PSP has one analog stick, and a stub offering Rx/Ry would
let a lane pass what the real SDK rejects. Taking the include back out
fails the new lane.

The 3ds audio drivers stay out: they want fifteen more symbols whose
libctru signatures cannot be checked from here, including a Result
returning entry point, and a guessed one is a lane that passes code
the SDK refuses.

Verified: 110 lanes, 0 failures, from a clean tree; the tree builds;
the stub header compiles standalone under -std=c89 -pedantic.
2026-09-21 18:55:23 +02:00
LibretroAdmin c5aa2ae368 compile-matrix: cover the platform drivers no other job compiles
griffin includes each of these behind its platform's #ifdef and the
desktop makefiles never build them, so a C89 slip or a missing
declaration in one waits for the console job. The stubs to reach them
are already in the tree; nothing joined the two.

Ten lanes, on the files the existing stubs reach today, with the same
warnings the driver lanes above them use - C89 declarations included,
because that is the rule these files are furthest from anyone
checking. GEKKO takes the vendored libogc headers ahead of its stub,
which carries only what libogc does not. dispserv_android already had a
lane and gains the declaration check rather than a second one;
psp1_gfx is covered by platform_video.

gx_joypad.c needed a fix to pass: WPAD_StickX declared its double
mid-function, where WPAD_StickY immediately below it declares the same
variable at the top. Reintroducing it fails the new lane, as do
injected implicit declarations in ctr_input.c and ps4_audio.c.

Files the stubs cannot reach are left out rather than papered over -
gx_audio, gx_gfx, ctr_gfx, wiiu_audio, gx2_gfx and the rest still want
headers no stub here supplies, and two of them collide with the host's
own nanosleep.

Verified: 108 lanes, 0 failures, from a clean tree; the tree builds;
hot-function-budget unchanged.
2026-09-21 18:47:35 +02:00
LibretroAdmin 2f07610657 retro_common_api: take ssize_t from <sys/types.h>
<stdio.h> carries ssize_t only for a profile asking for the BSD or
POSIX.1-2008 set, so a consumer building under a stricter one gets
"unknown type name 'ssize_t'" out of any public header here that uses
it, chd_stream.h among them. Include the header POSIX puts it in.

Scoped to __unix__ and Haiku alongside the existing Apple arm; console
toolchains define none of those and keep the path they have.
2026-09-21 18:45:59 +02:00
LibretroAdmin baa8536a17 libretro-common: ask for the POSIX profile the sources need
strdup(), strcasecmp(), fstatat(), O_CLOEXEC, M_PI, sigaltstack() and
syscall() all sit behind __USE_XOPEN2K8 or __USE_MISC, so a consumer
compiling the library under a stricter feature profile than the default
sees none of them. The Debian/Launchpad core packaging passes
-D_XOPEN_SOURCE=600, where ten of these translation units reach an
implicitly declared function; at -D_POSIX_C_SOURCE=200112L strdup joins
them, and an implicit strdup returns int, so the pointer is truncated on
LP64 rather than merely warned about.

Add retro_posix_source.h, which asks for that profile, and include it
first in the files that need it. The guards only raise the profile and
ask for what a build passing no feature macros already gets, so no path
the library selects changes: every touched object is byte-identical
under the default profile.

nbio_linux.c defined _GNU_SOURCE after its first include, where it had
no effect; retro_posix_source.h replaces it.

Verified: all 221 standalone-compilable libretro-common units clean
under no macros, _XOPEN_SOURCE 600 and 700, _POSIX_C_SOURCE 200112L and
200809L, and _GNU_SOURCE, with implicit declarations and int-conversion
as errors; the fifteen samples suites that link these units build and
pass; compile-matrix.sh gives results identical to before the change.
Building with -DRETRO_POSIX_SOURCE_CHECK_ORDER turns a late include into
an error, and was itself checked both ways.
2026-09-21 18:45:55 +02:00
libretroadmin bcd929ffe5 rh264: pictures decode concurrently, each waiting on the rows it reads
Given a thread pool, the decoder keeps up to four pictures in flight.
A picture's slices are queued as they arrive, on its own context, and
go to the pool when the next picture opens; the sequence's side - an
IDR emptying the DPB, reference marking, the DPB and the output queue
- is settled then, on the submitting thread, with the picture's rows
still counting up. Every place a picture reads a reference - the
inter prediction, the B predictions, and temporal direct's co-located
cells - works out the row the read reaches and waits on the block's
counter for it, and nowhere else. A picture due for output leaves
only once complete; the reorder depth is the pool's head start. Field
pictures and intra pictures stay on the submitting thread: a pair
fills one block a field at a time, and an intra picture's grid is
marked at completion. The waits sleep on a condition; nothing spins.
Without a pool, or with one thread, this is the decoder as it was.

The lossless oracle decodes every case four ways and holds each
byte-exact against ffmpeg: on one thread, with four contexts rotating,
with four pictures decoding concurrently on a pool, and concurrently
with every row's publication held back by a random spell so that the
readers wait for their rows rather than nearly always finding them.
That last run, and the sanitizers, found what they were for: a hook
on the submitter's picture rather than its own, a completion asking
the sequence's flag, reference counts that were not atomic, a field
view reaching half the rows it read, and the B predictions reading
their references past the wait altogether.

The MP4 container hands its pool to the H.264 decoder as it does to
the HEVC and VP9 ones, so the thumbnail's thread setting reaches all
three; a bench on one core is what it was, and the number that matters
comes from a machine with cores to give.
2026-09-21 18:34:38 +02:00
LibretroAdmin 7c9fa046fd (CC resampler) The Allegrex stream travels in the handle
The VFPU register file is per-thread context on PSP, and the two ends
of this backend are on different threads: audio_driver_init_internal()
creates the resampler on the main thread, and the threaded audio
pipeline runs process() on the audio worker. The constants pi and 1/pi
that init() issued, and the accumulator and position in c720/c730,
never reach the thread that resamples - it starts on whatever its own
context holds. One register file also cannot hold several instances,
so the frontend's resampler, the extra-channel ones and every mixer
voice were the same stream.

So the handle carries them: init() allocates it, process() issues the
constants per call and loads c720/c730 from it and stores them back
around the loop, and reset() and free() become the plain C the other
arms already have. Two vcst.s and a pair of quad accesses per call,
against a loop that runs per frame.

This is the crash reported on PSP: a stale position kept the loop
emitting after the input was spent, and the scalar float stores walked
out of the output buffer and off the end of user RAM. The bound in the
previous commit stops the walk; this stops the stale position.

The asm is compiled here only through gcc -fsyntax-only against the
PSP stub headers, which does not assemble the VFPU mnemonics. It wants
a devkitPSP build before it goes near master.
2026-09-21 18:29:04 +02:00
LibretroAdmin 5dbe67ced6 (CC resampler) Stop at the output the ratio asks for
struct resampler_data hands a backend an output pointer and no
capacity, so the frontend's own clamp is all that keeps a ratio inside
data_out - and audio_driver_bound_ratio() returns the ratio untouched
wherever the caller reports no capacity. The arms that can emit more
than one frame per input frame now stop at what the ratio asks for
plus the frame a carried phase adds, and a ratio no pair of sample
rates can name resamples nothing rather than guessing at one.

This is what keeps a bad phase inside the buffer instead of walking
memory: the upsample loops exit only on their own accumulator, so a
ratio or a position that never brings it back under one emits forever.

resampler_reset gains the contract: every backend's reported frames
against what the ratio asks for, across several calls so a carried
phase is in play, and CC against the ratios a clamp would have caught,
into an exact heap buffer so a sanitizer build sees a write past it.
Without this commit that lane is an ASan SEGV in the SSE upsample arm.
sinc and nearest run past data_out on the same ratios and are left for
their own change; the suite says so where it skips them.

The bound is free: best-of-7 over 20000 calls at 48/32 on the SSE arm
lands in the same 0.174-0.177 s band with and without it.
2026-09-21 18:28:59 +02:00
LibretroAdmin ab583a16b6 rwebaudio: declare memalign, and stop on a refused allocation
Three things in one function. memalign is called with no declaration -
<stdlib.h> does not carry it - and this driver is its own translation
unit in the emscripten build, not part of griffin's, so nothing hides
it: emscripten's clang makes an implicit declaration an error by
default. audioworklet.c, the sibling driver in the same directory,
already includes <malloc.h> for the same call.

The two mixing buffers were used unchecked. rwebaudio_write()
interleaves into them on every call with no test of its own, so a
refused allocation handed back a context that faults on first use;
init now fails and tears down instead.

And the driver context leaked when RWebAudioInit() failed, between the
calloc and the error return.

The allocator itself stays memalign: the buffers cross into the JS
side, and libretro-common's memalign_alloc keeps a header word below
the pointer it returns, which is not a swap to make on a platform this
cannot build.

Verified: the file compiles standalone under gnu99 and c89 with
-Werror=implicit-function-declaration, where it failed before, and the
tree builds. No samples/audio suite references it, so there is nothing
to run.
2026-09-21 18:26:30 +02:00
LibretroAdmin a1de2ae3bc librsound: declare perror and snprintf
librsound.c calls both and includes no stdio.h. It reaches a compiler
only through griffin's unity build, where stdio arrives 900 lines
earlier from compat_snprintf.c, so the two implicit declarations have
never fired - they wait for the include order to change or for the file
to be built on its own. Compiled standalone it is two
-Werror=implicit-function-declaration errors, which is a hard error on
devkitPPC rather than a warning.

The rsound driver's own comment also still pointed at librsound's
callback lock as the thing its ring avoids touching. That lock is gone;
the ring is what the handoff runs on.

Verified: librsound.c standalone clean under gnu99 and c89 with
-Werror=implicit-function-declaration, where it failed before; the tree
builds; and the new include is provably a no-op in the unity build,
which already has stdio.h by then.
2026-09-21 18:19:25 +02:00
LibretroAdmin 14e495b4bf libretro-common: ask for the POSIX profile vfs_implementation needs
O_CLOEXEC, fstatat() and dirfd() are POSIX.1-2008 and syscall() is a
glibc/musl extension, so a consumer compiling this file under a
stricter feature profile than the default sees none of them; the
Debian/Launchpad core packaging passes -D_XOPEN_SOURCE=600 and stops
on undeclared O_CLOEXEC. Request the profile the file needs before its
first system header instead, raising it only when what the consumer
asked for is lower.

What is requested matches a build passing no feature macros at all, so
every path this file selects stays as it is: the object code is
byte-identical under the default profile, and a -D_XOPEN_SOURCE=600
build now produces that same object.
2026-09-21 18:19:03 +02:00
LibretroAdmin 2d777c75fb video_driver: publish the viewport parameters only when they change
The snapshot is published once per frame as the catch-all for settings
toggles, and the video thread reads it every frame. What it carries
changes on a settings toggle, a rotation or an aspect change - never
during play - so the unconditional store spent a cache line transfer a
frame to say nothing, and took the line away from the reader to do it.
Compare first: fifteen relaxed loads, which leave the line where it is,
and the sequence bump and the stores only on a difference.

The slot count stops being a literal in two places, with headroom left
in the array so a parameter can be added without moving anything after
it.

Nothing notices if the compare goes - the values are still right and
every lane passes - so the harness watches the sequence counter, which
is the one thing that stands still exactly when the publish did
nothing: ten quiet frames must not move it, a changed aspect index
must, and the new value must reach the slot. Publishing
unconditionally fails the first; a slot that stops following its
setting fails the others.

Verified: compile-matrix 98/98, C89_BUILD from a clean tree, the
RARCH_MOBILE branch of the publisher, hot-function-budget,
stack-frame-sweep, lock_acquire_budget, thread_read_audit,
video_thread_state_check, and make check on plain, address+undefined
and thread builds plus 15 cycles.
2026-09-21 18:13:13 +02:00
LibretroAdmin ee3bb8ed40 retro_eventcount: say why the two cursors share a line
Padding epoch and waiters apart is the obvious-looking fix and it is
the wrong one, so record the measurement rather than leave the next
reader to rediscover it. Both sides read both cursors on every
operation, so separated, each pays two lines where one served.

Measured on 32 cores, shared against padded, medians over nine rounds:
where the operations punctuate real work, which is every caller in the
tree, no difference at all - 1.00 with the bracket inside a percent,
with one waiter or four. In a tight loop the direction is not even
consistent: one notifier and one waiter lose a third of the notify rate
separated, four waiters gain a seventh. The video ring is the former
shape, so the padding would cost it something for nothing.

retro_spsc pads its cursors because each of its sides reads only its
own; the note points at that difference.
2026-09-21 17:47:13 +02:00
LibretroAdmin 6fae03444c ctr: build the DSP audio driver against libctru 1.x
SYSCLOCK_ARM11 falls back to the SoC clock's multiple where libctru does
not define it, and the frame wait polls LightEvent_TryWait in short
slices where LightEvent_WaitTimeout is absent; the ctr-legacy image has
neither.
2026-09-21 17:37:45 +02:00
LibretroAdmin bc01dfd1be Threaded video: keep the two threads off each other's cache lines
Three places in thread_video_t had fields written by different threads
on one 64-byte line, so every write to either pulled the line across:
the snapshots the video thread publishes every frame sat with the head
of cmd_data, which a sender rewrites for every synchronous command; the
per-frame published flags sat with nonblock and the deferred ring's
head; and the ring's producer and consumer indices were four bytes
apart. A line of padding each way, so the separation holds wherever the
fields land rather than depending on an offset, at 192 bytes on a 22 KiB
struct.

Measured on a 6-core Windows workstation with a bench that models these
exact offsets, so its "shared" variant is the layout as it shipped.
Publishing during a burst of synchronous commands goes 2.13x
[1.93 .. 2.19 over 9 rounds] with no cost to the sender, 0.99
[0.96 .. 1.00]; the ring's throughput goes 1.20x [1.10 .. 1.33] on both
ends. A quiet frame - one command between two publishes - came out as
noise on both machines, so that part is not claimed.

The padding is the whole of the mechanism and nothing else notices when
a field added above one of the pads moves the field below it back onto
the shared line, so the harness asserts each separation by offset
against the struct the build actually has. Removing pad_published,
pad_flags or pad_ring each fails the lane by name.

Verified: compile-matrix 98/98, C89_BUILD from a clean tree, the
forced-atomic-backend loop over C11/GCC_NEW/SYNC/PS2/VOLATILE,
hot-function-budget, stack-frame-sweep, lock_acquire_budget,
thread_read_audit, video_thread_state_check, and make check on plain,
address+undefined and thread builds plus 15 cycles.
2026-09-21 17:30:09 +02:00
libretroadmin ab7eca949f rh264: pictures take contexts in rotation, and the oracle decodes every case at four
The finish comes apart into the picture's own completion - the slice
map for the filter, the rows the row hook did not filter, the intra
marks in the grid, the block's row count set to the whole picture,
the references let go - and the sequence's side: an IDR emptying the
DPB, reference marking, the picture entering the DPB and the output
queue. The first takes only the context and is what a pool thread will
run; the second runs in decode order on the thread that submits.
Allocation comes apart the same way: the DPB and the output queue are
remade only when the geometry changes, and a context is readied for
that geometry when its picture opens, not before its header is read.

With that a picture can take any context: the decoder holds four, a
new picture takes the next one round, and a field pair stays on the
context its first field took. rh264_video_set_contexts() sets how many
are in rotation; one is the decoder as it was. The pictures still
decode one after the other, so rotating changes which memory a picture
uses and nothing else - and that is the proof this sequence of
changes was for. The lossless oracle decodes every case at one context
and at four and holds both byte-exact against ffmpeg: a difference
would be a piece of picture state still in the decoder rather than the
context, shared between pictures the moment they decode concurrently.
There is none, and the check stays.
2026-09-21 17:09:21 +02:00
LibretroAdmin 2131c183fe overlay_on_screen_test: turn widgets off so a notification can't cover a square
A CI run failed with threaded video off: rows 377-418 of the bottom
right square were not magenta and its centre read (22, 22, 22). That
is 0x161616, BG_COLOR_DEFAULT, the background of a widget notification
box, which at 640x480 lands over that square. The runner reinitialised
a driver during startup, and the notification it raised was still up
when the screen settled and was grabbed.

The test only looks at the overlay, so set menu_enable_widgets to
false in its config.
2026-09-21 16:51:33 +02:00
LibretroAdmin 9c61c12805 tools: hold the lock-free paths to their acquisition count
The statistics the overlay reads, the viewport an input driver asks for
on every poll, the refresh rate the runloop asks for every iteration
and the audio thread's two loop conditions all carry their values in
atomics so the read costs no acquisition. Nothing stops the next change
from putting an slock_lock back, and a reinstated one breaks no test:
the suite passes and it shows up as a slower frame on someone's
machine.

So count them. The count is read off the relocations inside each
function's own block in the release objects, which catches a lock taken
through a helper the compiler inlined and misses one taken through a
call it kept - a floor, like tools/video_thread_state_check.py, and
worth having because every acquisition it reports is a real one. Three
of the counts are deliberately not zero: viewport_info writes read_vp
when the viewport has changed, the audio loop takes the lock on the
pass that parks or leaves, and the gather takes it once it has
something to retire.

Run by hand after a plain make. It reports which architecture it can
count on rather than passing silently where it cannot.

Negative-tested: an acquisition put back into presenter_stats, into
viewport_info's poll, into the audio loop's every pass, and into
video_driver_frame each fails its own line.
2026-09-21 16:50:29 +02:00
libretroadmin e45b786cbe rh264: a picture's reference lists are its own snapshots
A slice decoder read its reference lists through pointers into the
DPB slots. On one thread that is sound; with pictures decoding
concurrently a slot can be taken by a later picture while an earlier
one still decodes from it, and the reference would be pulled out from
under the read. Each picture's context now snapshots the frames its
lists name - a copy of the frame holding its own reference on the
sample and motion blocks - and the lists point at the snapshots.
Entries naming the same picture share one, and the finish releases
them. The counted blocks are what make that a pointer swap and a
count, not a copy.

With this a picture's decode depends on nothing in the decoder a
later picture can change: the slice decoders take their frame, their
parameter sets and their lists as arguments, the lists are owned, and
the row counter is on the block. What remains for pictures to decode
concurrently is the sequencing. Byte-exact on every case of the
lossless oracle, with no reference read short of its rows.
2026-09-21 16:41:55 +02:00
LibretroAdmin e69fd2efaf audio: take the lock off the audio thread's loop
The loop took thr->lock every pass to read two booleans that are false
while audio is playing. They become atomics: the loop tests them
without the lock and takes it only on the pass that has something to
coordinate - leaving, or parking on a stop. A writer still holds the
lock while it changes one and signals, which is what keeps the change
and the wakeup atomic against a waiter, and the stop path re-tests
under it so a start that lands in between is not stopped and started
again for nothing. alive() and apply_control() read them as atomics
too, where they read a plain bool before.

That plain read was a race, and samples/audio/thread_handshake now
reaches it: the scripted device refuses a write, which is the one
place the audio thread marks the device gone, and the frontend then
asks whether it is alive. The lane also feeds the callback through the
wrapper's own write() rather than the inner driver's, which is the
path the frontend uses. On the old code TSan reports the race between
audio_thread_alive() and audio_thread_write(); with the atomics it is
clean, and the lane holds the device-gone contract - reported, stop
returns without waiting for an acknowledgement, free() joins.

Verified: thread_handshake and pipeline_wakeups, the two suites that
compile the wrapper, on plain, address+undefined+leak and thread
builds; compile-matrix 98/98; C89_BUILD from a clean tree; the
forced-atomic-backend loop over C11/GCC_NEW/SYNC/PS2/VOLATILE;
hot-function-budget; stack-frame-sweep; thread_read_audit; and
samples/gfx/threaded_video make check.
2026-09-21 16:40:08 +02:00
LibretroAdmin 87ba61a2b6 CI: run every retro_procbarrier tier on Linux, Windows and macOS
The Linux samples job runs retro_procbarrier_test once, so it only
ever exercises membarrier. The page-flip and signal fallbacks, the
Windows FlushProcessWriteBuffers path and the Darwin paths never ran
in CI at all; the Darwin code had never been compiled.

Add procbarrier-tiers.yml, which builds retro_procbarrier_test and
retro_asym_eventcount_test on Linux x86_64/arm64, Windows x64/arm64
and macOS arm64/x86_64, and runs both once per tier the platform has.

run_tiers.sh checks the "tier:" line of each run, not just the exit
code: a forced tier the platform lacks falls back to the natural one
without an error, which would otherwise pass unnoticed. With
REQUIRE_SMP=1 it also fails on a one-CPU runner, where the fence
checks cannot catch a missing barrier.
2026-09-21 16:36:39 +02:00
libretroadmin bed37a08f4 rh264: the picture's state is its own, and its block counts the rows that are final
Everything that belongs to the picture being decoded - the working
frame, its motion grids, the slice bookkeeping, the slice scratch, the
NAL unescape buffer - moves into rh264_pic_ctx, and the decoder holds
one of them for now. What stays in rh264_video is the sequence:
parameter sets, the DPB, picture order, reference marking, the output
queue. The slice decoders already took their frame, parameter sets and
reference lists as arguments and touched the decoder only for the
sequence, so one of these per picture in flight is what lets pictures
decode on different threads, and a scheduler is all that is missing.

The block behind a picture's planes counts the macroblock rows that
are final. The row hook counts them up as each row's filtering
completes - after row r is filtered everything above it is final,
while r's own last lines are still touched through the edge it shares
with r+1 - and the finish counts the whole picture. The one place a
reference is read works out which row the read reaches, the block's
last line displaced by its vector and the six-tap filter's three past
it, and asks the block for that row. On one thread a reference is
complete before it is read and the ask is answered at once; a picture
decoding on another thread will wait there, and nowhere else. The
lossless oracle asserts that on one thread no read ever finds its
rows short, across every case: a short read would have a threaded
decoder waiting on a row it had already been handed.
2026-09-21 16:12:50 +02:00
LibretroAdmin b74767fc76 Threaded video: publish the viewport and the refresh rate
Both come from the wrapped driver on the video thread, which is their
only writer, and both are read from elsewhere every frame - an input
driver asks for the viewport on each poll, the runloop asks for the
rate on each iteration. Publish them instead of locking for them: the
viewport as a seqlock over six slots, the rate as one word of float
bits. read_vp still has to follow what was reported, because
CMD_READ_VIEWPORT compares the driver's own viewport against it, so
viewport_info writes it when it changes - a resize - and the steady
state takes no lock at all. Every reporter writes the same published
value, so a compare that races another's write converges on it.

thr->vp goes with them; the snapshot is what callers read now.

The harness lane asserts the reported values arrive, that read_vp
follows a resize, and that the read touches no lock: a helper thread
holds thr->lock for 200 ms and the viewport, the rate and the four
statistics readers are timed inside that window, so a reader that
still takes the lock is delayed rather than deadlocking. Putting
either read back under the lock fails it at 199 ms; swapping the
width and height slots, dropping the per-frame publish, never
publishing the rate, and stopping read_vp from following each fail
their own check.

Verified: compile-matrix 98/98, C89_BUILD from a clean tree, the
forced-atomic-backend loop over C11/GCC_NEW/SYNC/PS2/VOLATILE,
hot-function-budget, stack-frame-sweep, thread_read_audit,
video_thread_state_check, and make check on plain, address+undefined
and thread builds, plus 15 cycles with no lane skipped.
2026-09-21 16:11:49 +02:00
LibretroAdmin 9b398489e1 net: declare struct pollfd only where the SDK does not
winsock2.h declares struct pollfd when _WIN32_WINNT >= 0x0600. Gate
the libsmb2 compat.h declaration on that, so Vista+ targets take the
SDK's and the Xbox XTL, legacy winsock.h and pre-Vista SDKs take the
compat one. Fixes C2011 on the UWP build.

net_compat.h supplies the same declaration for pre-Vista Win32
targets, where socket_poll() needs it and no builtin SMB client is
compiled: msvc2003 builds at 0x0400, msvc2005 at 0x0410.

tools/win32-pollfd-check.sh cross-compiles both headers in both
include orders from 0x0400 to 0x0A00, plus net_socket.c on its own.
Run it after touching either header.
2026-09-21 16:11:15 +02:00
libretroadmin 229c1cecbe rh264: a reference's own motion grids are made when a field needs them
Every DPB slot carved two full motion-vector grids into its meta block
when the decoder was set up - thirty-two megabytes a slot at 4K - and
since a frame picture shares the working grid into its slot, nothing
read them: they were allocated and zeroed for the field pairs alone,
which copy into grids of their own. The slot makes its two grids the
first time a field is copied into it and keeps them with the frame;
a stream of frame pictures never allocates them.

The preview bench takes BENCH_MAX, to profile a stream's start on its
own: what it showed on a 4K recording was that a second picture taking
most of a second was a 1.2 MB P-frame's entropy decode, not the
decoder's setting up.
2026-09-21 15:46:33 +02:00
LibretroAdmin 1371dc922d Threaded video: publish the overlay's statistics as a seqlock
The statistics overlay reads four groups of wrapper numbers once a
frame, and each read took thr->lock - a write-share on the busiest
lock in the wrapper, from the runloop thread, for data it only reads.
Publish them instead: the video thread stamps the snapshot inside the
region it already holds that lock for, so publishers stay serialised
by it, and a reader only loads. 64-bit counters travel as two
int-wide halves, low first. core_time and display_pacing come from
the main thread, so they ride a frame behind; everything else is this
frame's.

samples/gfx/threaded_video gets a lane that brackets each published
field against the field it came from, and asserts the swap count
across a carry out of the low half. The flags word is only meaningful
where a bit is set, so the repeat, display-phase and display-pacing
lanes call the same check with their own bools true. Negative-tested
per bit and per slot: each dropped half, swapped slot pair and
dropped flag bit fails the lane.

Verified: compile-matrix 98/98, C89_BUILD from a clean tree, the
forced-atomic-backend loop over C11/GCC_NEW/SYNC/PS2/VOLATILE,
hot-function-budget, stack-frame-sweep, thread_read_audit,
video_thread_state_check, and make check on plain, address+undefined
and thread builds.
2026-09-21 15:33:30 +02:00
libretroadmin 827ed072db Animated previews: the feed keeps ahead of a decoder that is catching up
The windowed feed reads half a megabyte a tick, sized for a decoder
that takes one sample a tick. A decoder catching up takes three or
four - it passes over the pictures nothing references - and a fast
one takes them at whatever rate it decodes, so either eats through the
two seconds of lookahead faster than the feed refills it and then
stands at the wall for the forty ticks a refill takes: on a 4K
recording, a stall near a second at every group of pictures. The
pacing holds only while the feed is comfortably ahead now; once the
resident lookahead is below half its target the tick extends unpaced,
the burst a lap or an open already pays, and the decoder never sees
the wall. On the recording that showed it, the stalls at each group
went from eight hundred milliseconds to two hundred, which is one
picture's decode.

The preview sample's paced check bounds the stall now, on the
B-frame fixtures, and its audio checks apply only when audio was asked
for. The bench names any frame that took over a third of a second.
2026-09-21 15:32:28 +02:00
grokiepoki f670c27aa3 net: implement Win32/Xbox socket_poll() with select()
WSAPoll is Vista+ only. 9x, XP, and Xbox XTL do not export it.

Keep socket_poll() on every platform. On Win32 and Xbox implement it
with select() and Microsoft POLL* bits so socket_wait() and SMB keep
working. POSIX/Vita/Gekko/PSL1GHT backends are unchanged.

libsmb2: stop mapping poll to WSAPoll. Use the existing select()-based
poll() in compat.c on all Windows targets, including desktop Winsock2.
2026-09-21 15:31:34 +02:00
LibretroAdmin 6702926dd9 icloud: report failures as failures, and keep copies under non-destructive sync
The CloudKit driver:

- reported success for a read whose local file could not be written
  (with no file, which cloud sync takes as "not on the server" and
  drops from the server manifest) and for a record with no readable
  data (with an empty file, which cloud sync takes as the save);
- saved a new record when the lookup before an upload failed, leaving
  a duplicate of a record that may well exist;
- reported a delete as done before the deletes were sent, and as a
  success whatever became of them;
- ignored non-destructive sync: deletes removed records for good and
  uploads replaced their data.

Reads now fail unless the data was loaded and written in full. Uploads
fail on a failed lookup. Deletes report once every record has been
dealt with, with the result. With destructive sync off, a delete
renames the record's path to <path>-<yymmdd-hhmmss> and an upload that
replaces a record first saves one under that name holding the old
data, not replacing anything if that fails, as the other drivers keep
their copies. The server manifest is not kept. A duplicate-record log
line also passed an NSString to %s.

samples/network/icloud_cloudkit includes icloud.m and replaces
CloudKit with an in-memory database by overriding +[CKContainer
defaultContainer] in a category, using real CKRecord, CKAsset and
CKQuery objects. It checks each case above and that every request
reports once. Against the old driver 8 checks fail. Runs in CI macOS
under ASan+UBSan.
2026-09-21 15:22:18 +02:00
LibretroAdmin dadbd009d9 (Vulkan) Negotiation interface v3: a presentation queue in the graphics family
A presentation_queue that is another queue of queue_family_index is only
valid for a frontend that takes one, so it gets a version of its own.
GET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_SUPPORT reports 3, and a core
may hand back that queue only when it sees 3 or higher. Frontends below
that fail context creation on any presentation_queue that is not the
graphics queue.

Acceptance stays version-agnostic: a core that provides the queue without
asking is still honoured when the family matches.
2026-09-21 15:17:40 +02:00
LibretroAdmin 5bb3e201a5 google_drive: keep the replaced copy when an upload overwrites a file
With destructive sync off, a Google Drive delete keeps the server's
copy, renamed in place to <name>-<yymmdd-hhmmss>, but an upload
replaced the file's content outright, so the setting protected against
deletes and not against overwrites.

An upload to an existing file now asks Drive to copy it (files.copy,
server-side) to <name>-<yymmdd-hhmmss> in the same folder, the name a
delete uses, before the PATCH. A failed copy stops the upload rather
than overwrite without a backup. A file that does not exist yet has
nothing to keep, and the server manifest, rewritten every sync, is not
backed up.

A 401 on the PATCH now retries only the PATCH. It used to go back
through the whole update, search included, which with the backup would
take it a second time; the file ID is known by then.

samples/network/gdrive_backup includes google_drive.c and plays the
Drive API request by request: copy before PATCH, from the file being
replaced, named and placed as a delete names it; a failed copy stopping
the upload with one failure report; a 401 on the copy and on the PATCH
each refreshing the token once; destructive sync, a new file and the
manifest not copied. Against the old driver 5 checks fail. Runs in CI
Linux samples/network under ASan+UBSan and TSan, as does the SMB
harness.
2026-09-21 15:11:28 +02:00
LibretroAdmin ff1d6571b3 smb: upload through a temporary file and keep the replaced copy
smb_update() opened the server's file, truncated it and wrote. A write
that failed part way - a dropped connection, a full share - left the
server holding a truncated save, in either sync mode. And with
destructive sync off, a delete kept the old file as
<path>-<yymmdd-hhmmss> while an upload overwrote it in place, so the
setting protected against deletes and not against overwrites.

Uploads now write <path>.rauploading and rename it into place once it
is complete. Before that rename, the file being replaced is renamed to
the backup name a delete uses (non-destructive) or removed
(destructive); if the final rename fails, it is put back. A failed
write, a backup that cannot be made or a failed final rename all leave
the old file exactly as it was and no temporary file behind, reported
as one failure. The server manifest is never backed up.

Also: the local file is read from its start rather than wherever the
stream was left, a short read fails the upload, and a zero-byte write
from the server no longer loops forever.

samples/network/smb_backup runs smb.c against an in-memory libsmb2
share and checks what the share holds after each case: new content in
place and the old kept beside it; no backup for destructive sync, a new
file or the manifest; the old file intact after a failed write, backup
or final rename; an empty upload; the configured subdirectory. Against
the old driver 13 checks fail.
2026-09-21 15:11:23 +02:00
LibretroAdmin 05f19857db s3: honour non-destructive sync by backing up before delete or overwrite
The S3 driver never read cloud_sync_destructive: a delete removed the
object for good and an upload overwrote it, so turning destructive
sync off did nothing for S3 users. WebDAV, Google Drive and SMB keep
the server's copy in deleted/ on delete, and WebDAV now on overwrite.

With the setting off, a delete or upload first copies the object to
deleted/<path>-<yymmdd-hhmmss>, the name WebDAV uses, with CopyObject
(a PUT on the new key naming the source in x-amz-copy-source), which
is server-side. The source is /<bucket>/<key> for both addressing
styles: a path-style endpoint's canonical URI already is that, a
virtual-hosted one gets the bucket in front. A 404 means there is
nothing to keep; any other failure, including a 200 carrying an
<Error> body, which CopyObject can answer, stops the delete or upload
rather than lose the copy. The server manifest is not backed up.

S3 requires every x-amz-* header to be signed, so the SigV4 code takes
an optional copy source and signs it in its sorted place. The
signature for a CopyObject request and for a plain GET was checked
against botocore's SigV4 for the same inputs and matches.

samples/network/s3_backup includes s3.c and plays the server request
by request: copy first, into deleted/, with the right source for
virtual-hosted and path-style endpoints and x-amz-copy-source signed;
404 not an error; a failed or <Error> copy stopping the operation with
one failure report; destructive sync and the manifest not copied.
Against the old driver 6 checks fail. Runs in CI Linux samples/network
under ASan+UBSan and TSan.
2026-09-21 14:52:37 +02:00
libretroadmin e138e37f55 Previews: the decoders recycle their blocks, a dropped picture's slot passes, and a paced check
Three things the 4K preview was doing wrong on Windows, none of them
visible to a decode oracle.

The shared plane and motion blocks are recycled rather than freed.
The working frame takes a fresh block for nearly every picture - the
one it holds is a reference's or a queued picture's by then - and a
4K block is twelve megabytes: on the C runtime MinGW builds against
that is a page-zeroed mapping from the kernel and back, twice per
picture, where the swap it replaced allocated nothing. A released
block goes on a short free list by length and the next of that length
comes from there, so the steady state allocates nothing again.

A picture passed over to catch up left the presentation index where
it was: the decoder returns 0 for it as it does for a picture held
back for reordering, and the container took it for one. From then on
every shown frame read its duration from the slot before its own. The
decoders say when a call passed a picture over and the container lets
its slot go by, as it does for a refused one.

The preview sample's oracle animated flat out, which decodes six
hundred 4K pictures twice and shows nothing about playback. PACE_HZ
polls it at a display's cadence, which is how the thumbnail is driven,
and reports the picture changes and the longest run of polls in which
the picture did not change - the stall a viewer sees.
2026-09-21 14:45:06 +02:00
LibretroAdmin 67e3aef27d webdav: back up the server's copy before an upload replaces it
With destructive sync off, a WebDAV delete moves the server's file
into deleted/, but an upload overwrote it in place: the setting
protected against the server's copy being removed and not against it
being replaced, which is the more likely way to lose a save (an older
or broken save from another device overwriting a good one). The code
carried a TODO/FIXME for exactly this. The local side already keeps
cloud_backups/ before a fetch replaces a local file.

An upload now copies the server's file to deleted/<path>-<yymmdd-
hhmmss>, the name a delete uses, before the PUT. COPY rather than
MOVE, so the server keeps its file until the PUT replaces it, and an
upload that fails after the backup leaves the server as it was. A 404
means there is nothing to back up yet; any other failure stops the
upload rather than overwrite without a backup. The COPY and the PUT
each get their own Digest retry. The server manifest is rewritten
every sync and is not backed up; its name moves to
cloud_sync_driver.h as CLOUD_SYNC_SERVER_MANIFEST so the driver can
recognise it.

Adds task_push_webdav_copy() beside task_push_webdav_move() in both
HTTP backends, sharing the Destination-header code.

samples/network/webdav_backup includes webdav.c and plays the server
request by request: backup before PUT, into deleted/ named as a delete
names it, 404 not an error, a failed backup stopping the upload with
one failure report, destructive sync and the manifest not backed up,
and a Digest retry each for COPY and PUT. Against the old driver 5
checks fail. Runs in CI Linux samples/network under ASan+UBSan and
TSan; the other WebDAV harnesses gain the new stub.
2026-09-21 14:40:01 +02:00
LibretroAdmin a513f2b3e3 samples/gfx: put the wrapper's overlay path under test
Both overlay passes skipped before they reached a single assertion:
the null driver has no overlay_interface, so the lane got its
textures and then stopped at "no load_textures". Everything the lane
describes - the page of borrowed textures, the switch that uploads
nothing, the disable-before-unload order - was never run in CI, and
neither was any of the wrapper code behind it.

So give the driver an overlay back end. It goes on video_null itself
and BEFORE the mode switch, because the wrapper decides at init
whether it has an overlay to offer from the driver it is about to
wrap: with one there, the threaded pass runs the real path, main
thread to command ring to the video thread's captured table, and
what the driver is told can be compared with what the frontend
asked for.

That comparison is what the pass now makes. Two of the checks are
against wrapper code nothing else in the tree touches, and both were
negative-tested:

  - an alpha set cannot be lost. The set is fire-and-forget: the
    value goes into an atomic and raises a flag the video thread
    clears with an acquire exchange BEFORE reading the values, so a
    set that lands mid-apply is applied whole next pass. The lane
    fires one from inside the apply - the driver's set_alpha runs on
    the video thread, after the clear and after image 0's value was
    read - and asserts it still arrives. Clearing the flag after the
    read instead loses it: "an alpha set from inside the apply was
    lost: the driver last saw 0.500, not 0.375".

  - the pack's textures are retired, not released. Unloading under
    the wrapper must NOT hand the driver a release, because the
    in-flight frame may still name the texture; it goes on the retire
    list and the video thread frees it once it has drawn the frame
    that carries it. Releasing inline instead fires "3 of the pack's
    textures were released on the main thread instead of being
    retired", and the named handles are then each asserted released
    once the frames have run.

The third check covers the page switch itself: the handles that
arrive are the frontend's own, in order, and the uploading load()
never runs. Forcing the fallback fires six lines, from "load_textures
refused page one" through "page two arrived as (0, 0), not (5002,
5003)".

With GFX_INSTRUMENT=1 the overlay baseline reports real numbers for
the first time - 6 loads, 6 unloads for 4 pages over 2 passes - where
it used to print zeroes, which also means the pre-existing bound on
uploads per page switch is finally being exercised.

Verified: make check clean on plain, address+undefined, thread and
GFX_INSTRUMENT=1 builds, and over 25 cycles; the harness TU clean
under -Wall -Wextra -Werror on the tree's own compile line; all three
negative tests fail as quoted.

The harness now has no skipped lane left on the null driver.
2026-09-21 14:39:40 +02:00
LibretroAdmin 6aa1b1b779 icloud_drive: report every request exactly once
Cloud sync counts its outstanding requests and waits for each one to
report, so a driver must call the completion handler exactly once per
request. The iCloud Drive driver broke that both ways:

- a read whose local destination could not be created never called
  it, so the sync waited forever;
- a read whose cloud file existed but could not be loaded, and an
  upload whose local file could not be read, reported failure and then
  carried on and reported again, decrementing the outstanding count a
  second time.

It also treated an unavailable iCloud container as "file not
present": a read reported success with no file, which cloud sync now
takes as the server not having the file and drops from the server
manifest, and a delete reported success. Nothing was checked, so all
three now report failure. A short write of the downloaded data is a
failure too.

samples/network/icloud_drive includes icloud_drive.m and redirects
the container to a temporary directory by overriding
URLForUbiquityContainerIdentifier: in a category, so the driver runs
unchanged; a nil answer stands for iCloud being unavailable. It checks
that every request reports once with the right result, for the paths
above and the ordinary read, missing file, upload and delete. Against
the old driver 8 checks fail. Runs in CI macOS under ASan+UBSan.
2026-09-21 14:18:12 +02:00
LibretroAdmin 9867add40b webdav: retry a Digest challenge once, and rewind a retried upload
Every WebDAV request callback retried when the server answered 401
with a Digest challenge, and nothing limited how often. That is right
for a stale nonce, but with a wrong password the server answers every
retry with a new challenge, so the first request of a sync - the
OPTIONS probe - looped forever and the sync never reported anything.
Allow one retry per request (webdav_retry_auth()); a second challenge
now fails the request with an error naming the credentials. Each
MKCOL in a directory walk counts as its own request.

A retried PUT also sent garbage. webdav_do_update() reads the upload
into memory, leaving the file at its end, and the retry read it again
from there: it got nothing and sent a buffer of the right length full
of uninitialised memory, replacing the save on the server whenever a
nonce expired mid-upload. Rewind before reading, and fail the upload
on a short read.

samples/network/webdav_reauth includes webdav.c and plays the server
for OPTIONS, GET, PUT, DELETE, MOVE and MKCOL: a server that keeps
rejecting the credentials must get exactly one retry and one reported
failure, a single stale-nonce challenge must still be retried and
succeed, and every PUT must carry the file's bytes. Against the old
code every request type loops and every retried PUT sends the wrong
bytes. Runs in CI Linux samples/network under ASan+UBSan and TSan.
2026-09-21 14:18:10 +02:00
libretroadmin fa823c3052 rh264: a picture has one home - reference, output and working frame share it
A finished frame picture was copied plane by plane into the DPB when
it was a reference and swapped into an output slot when it was to be
shown, and its motion-vector grid was copied into the slot twice, once
per parity. At 4K that is twelve megabytes of samples and two grids
per reference picture, and five per cent of the decode was memcpy.

The plane block and the working grid now carry a reference count in a
header ahead of the data. A frame picture's block is shared into the
DPB slot and the output slot rather than copied, its grid is shared
into the slot for both parities, and the working frame takes fresh
blocks on its way into the next picture only while the ones it holds
are still someone else's. Field pairs, which fill a block a field at
a time, keep the copy, into grids and samples the frame owns - a
field can never land in a block a queued picture still reads. The
lossless oracle holds every case byte-exact against ffmpeg, plain and
under the sanitizers, with every block released by its last holder.

Beyond the memcpy this is the shape frame threading needs: a picture
that lives in one place for its whole life is one a row counter can
sit on, and one a picture decoding on another thread can wait for a
row at a time. Under the copy model a reference did not exist until
the copy at the finish, and there was nothing to wait on.
2026-09-21 14:18:06 +02:00
LibretroAdmin 9e083a1d9f samples/gfx: give the harness driver a texture back end
The surface lane stopped at "driver made no texture" on the null
driver, which has no load_texture at all, and returned before the
three things it exists to check: that a streaming surface loads ONE
texture and keeps it across every frame, that each queued submit is
released exactly once, and that freeing a surface with a frame still
in flight releases its texture rather than leaking it. Everything
after the early return - the 30-submit loop, the in-place handle
check, the whole direct half, both instrument baselines - was
unreachable in CI.

So lend the driver a back end. Handles carry a tag and are tracked
one by one, so the checks name the surface's own texture and are not
disturbed by what else the frontend uploads through the same poke
(the menu's framebuffer comes through it). It goes on per video mode,
because each switch reinits the driver and rebuilds the poke from the
real one: threaded it sits on the wrapped driver, so the worker's
uploads run through it; direct, on the driver's own poke.

Two checks the back end makes possible are new, and are the ones that
have teeth:

  - the surface's texture is named and asserted released after a free
    with a frame in flight. ASan cannot see this leak: a texture
    handle is a driver-side resource, not an allocation.
  - the load count is bounded at one for the whole run, in both
    halves.

Both were negative-tested. Dropping the unload from the dying branch
of gfx_surface_done() fires the first ("1 textures still loaded after
the threaded half (1 loads, 0 unloads)"); clearing can_update on
every completion fires the second ("31 texture loads for one
streaming surface") along with the pre-existing in-place check, which
had never once run.

With GFX_INSTRUMENT=1 the lane now reports a real budget for this
driver instead of nothing:

  surface, threaded: 1 loads, 31 updates, 32 posts (0 allocated), 0 copies
  surface, direct:   1 loads, 10 updates, 1 unloads, 0 posts, 0 copies

Verified: make check clean on plain, address+undefined, thread and
GFX_INSTRUMENT=1 builds; the harness TU clean under -Wall -Wextra
-Werror on the tree's own compile line; both negative tests fail as
quoted.

The two overlay lanes still skip: they need an overlay interface with
load_textures, which the null driver also lacks. That is a separate
piece.
2026-09-21 14:07:29 +02:00
LibretroAdmin 2bb0215ddd webdav: check callback state before using it
webdav_read_cb(), webdav_update_cb(), webdav_delete_cb() and
webdav_backup_cb() dereferenced webdav_cb_st->path to log a failure,
and read and delete again on the reauth path, before the NULL check
that came further down. Check once at the top and return, as
webdav_stat_cb() already does, and drop the checks that can no longer
fail.
2026-09-21 13:39:05 +02:00
LibretroAdmin d997fe013b cloudsync: stop refetching files the server does not have
A file listed in the server manifest but missing on the server (such
as one uploaded under a name cut short at '#' before the WebDAV path
encoding fix) came back from the driver as success with no file.
task_cloud_sync_fetch_cb() logged that as "Failed to write file from
server", kept the entry in the server manifest and failed the sync,
so the same fetch failed on every sync after.

That result was ambiguous in the drivers too: WebDAV and Google Drive
also returned success with no file when the download could not be
written locally, and when the file on the server was empty. Both now
hand back an open RFILE for any found file, empty included, and
report a local open or short write as a failure. Success with no file
now means only "not on the server", which is what SMB, S3 and
iCloud Drive already meant by it.

task_cloud_sync_fetch_cb() then drops such an entry from the server
manifest and marks it for upload, leaving the local file and local
manifest alone: if this device has the file, the next sync sees it
as new and uploads it, which repairs the server.

Since an empty server manifest now arrives as an empty RFILE rather
than none, treat an empty one as no manifest, as an empty body was
before; rjson rejects empty input.
2026-09-21 13:39:01 +02:00
LibretroAdmin 182c6cb6c6 cloudsync: write manifest.local through a temporary file
The local manifest was rewritten in place, so a crash, a full disk or
a power cut part way through left it empty or truncated. The last fix
makes the next sync discard such a file, but that costs the sync its
record of the previous state, and every differing file then goes
through conflict resolution.

Write manifest.local.tmp and rename it over manifest.local only once
it is complete. filestream_rename() replaces an existing destination
on every platform, including Windows. If the write or the rename
fails, the temporary file is removed and the old manifest is left in
place, which is the safe outcome task_cloud_sync_commit_local_manifest()
already describes: the next sync re-examines the same files.

libretro VFS has no fsync, so on filesystems that do not order a
rename after the data it replaces, a power cut can still lose the
new manifest; the old one, or the discard path, covers that.
2026-09-21 13:38:53 +02:00
LibretroAdmin 7d158390ae s3: log the transport failure reason
task_http passes the failure reason (stage plus code, such as
"ssl_connect_failed (-0x7780)") to every transfer callback as err,
but every S3 callback discarded it with (void)err, so a transport
failure was logged only as "HTTP -1". Pass err through to
s3_log_http_failure() and s3_multipart_fail() and print it when there
is no status, the way webdav_log_http_failure() already does; the
"Did not get HTTP data" warnings include it too.
2026-09-21 13:30:59 +02:00
LibretroAdmin 32d5dbe1d7 net_http: report the OS socket error for socket stages
net_http_failure() gave a code only for the TLS stages, so a plain
connection refused, timed-out connect or failed send reached the log
as "socket_connect_failed" with nothing to say why.

Record errno (WSAGetLastError on Windows) for socket_create_failed,
socket_connect_failed and socket_send_failed, right after the failing
call and only for the first failing stage, as the TLS code already is.
task_http prints positive codes as "(111)", so the log line becomes
"Download failed: socket_connect_failed (111)."

socket_connect_with_timeout() now leaves the reason in errno too: a
non-blocking connect reports a refused or unreachable peer through
SO_ERROR, and a timeout through nothing at all, so both are copied
over (SO_ERROR value, or ETIMEDOUT/WSAETIMEDOUT). This is limited to
platforms that keep socket errors in errno or WSAGetLastError; PS3,
Vita, Wii U, GameCube/Wii and 3DS keep theirs elsewhere and still
report 0 (no code).

Receive failures are left without a code: a peer closing the
connection sets no errno, so any value there would be stale.
2026-09-21 13:30:55 +02:00
libretroadmin 58ddb6118c rh264: a row is deblocked once the row below has read it
The decoder reconstructed a picture whole and then filtered it whole,
one sweep over every macroblock at the end. Each slice decoder now
calls a hook as it completes a macroblock row, and the row above it is
filtered then: intra prediction reads unfiltered neighbours, and by
the time row r is complete it has read row r-1's last line, so the
filter can have it. Macroblock order is what it was, so the picture
is the whole-picture pass's, macroblock for macroblock - the lossless
oracle holds every case byte-exact against ffmpeg - and the row is
filtered while it is still in cache. Pair-scanned and field pictures
keep the whole pass, as does whatever a second slice leaves unfiltered.

What the boundary strengths read matters here: pic_mvg is the
picture's motion-vector grid, folded from the working grid once each
slice is done, and mid-slice it still holds the last picture's. The
hook reads the working grid; the finish reads pic_mvg, which by then
is the same.

A row that is filtered is final, and rows become final one at a time
from the top: that is what a picture decoding on another thread will
wait on, and the reason for this step.
2026-09-21 13:29:59 +02:00
LibretroAdmin 60766518d7 Threaded video: pin the drain that keeps frame.lock uncontended
samples/gfx/threaded_video gains a menu-texture lane. The harness driver
had no set_texture_frame at all, so nothing in it exercised the handoff
or the three texture lanes that skip; this one supplies the receiving
end and a slow frame() to make the timing visible, and asserts what
actually holds: with the menu texture enabled, video_driver_frame()
returns with the ring drained, and the driver is handed one push's
pixels rather than a mixture. Removing that drain fails the lane on 12
of 12 menu frames.

That drain is also the correction to what the previous commit claimed.
It said a main-thread set_texture_frame() had been parked on a frame and
its swap on every RGUI frame. It had not: the drain leaves the worker
idle before the next iteration's push, so the lock was never contended
on that path. Narrowing it still stands - set_texture_enable() and
apply_state_changes() arrive on a user action with no push behind them
and were waiting out a render - but the comments now say what is true
instead, and name the drain the lane depends on.

The lane checks the tear case too, cheaply, though that half has no
demonstrated teeth: with the drain removed and the handoff unlocked, the
window between the push's memcpy and the driver's read stayed too narrow
to reproduce here.
2026-09-21 13:28:29 +02:00
Chris BradelandLibretroAdmin 4ae32cee45 Fix WebDAV cloud sync crashes and encoded save paths (#19598)
* cloudsync: reject invalid manifests

Check the JSON parser result before sorting a manifest. Discard partial manifests and stop the sync with an error instead of dereferencing an incomplete entry.

* webdav: handle missing files and encode sync paths

Treat HTTP 404 as a missing file without writing the server's error page to disk. Encode sync paths separately from the WebDAV base URL so filenames containing '#' are sent as filenames, not fragments.

* webdav: keep '/' as separator in deleted/ backup path

fill_pathname_join_special(dest, "deleted", path) appends
PATH_DEFAULT_SLASH when the directory has no slash of its own. On
Windows that is a backslash, which net_http_urlencode then sends as
%5C, so every backup MOVE targeted "deleted%5C<path>" instead of the
deleted/ collection. Pass "deleted/" so the existing '/' is kept.

* cloudsync: recover from an invalid local manifest

manifest.local is rewritten in place, so a crash, full disk or power
loss during the write can leave it empty or truncated. rjson rejects
empty input, so with the new manifest validation every later sync
stopped with "Invalid manifest" and the only way out was switching
sync drivers, which is what deletes the file.

Log a warning, delete the bad local manifest and continue as a first
sync. With no local manifest, files that differ between local and
server go through conflict resolution instead of being overwritten.
An invalid server manifest is still fatal.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-09-21 13:22:18 +02:00
LibretroAdmin cc9f8777dc Threaded video: hold frame.lock for the handoff only
The video thread takes frame.lock for the thread_update_driver_state()
that applies the menu texture and apply_state_changes, and releases it
before the render. A driver takes the texture's pixels inside its own
set_texture_frame(), uploading or copying them there rather than keeping
the pointer, so the staging buffer is free as soon as the update returns
and the render needs nothing the lock guards.

That is what a main-thread set_texture_frame() now waits for: the
handoff, not a frame and its swap behind it. RGUI pushes a texture every
frame the menu is up, so with the software menu open the main thread was
parked on a full render and vsync each frame - hardest on the handhelds
where RGUI is the default. The repeat path drops the lock too: it
presents a retained frame and touches no menu texture.

samples/gfx/threaded_video passes under ASan+UBSan and under TSan, but
it does not cover this handoff: its null driver has no set_texture_frame,
so the video thread clears the update flag without ever reading the
buffer, and removing the lock outright still passes. Giving the harness
driver texture entry points would cover it, and would also un-skip the
three texture lanes that skip today.
2026-09-21 12:52:59 +02:00
libretroadmin 5929cdd3a6 gfx_thumbnail_preview: builds on MSYS2/MinGW, so the bench runs where the question is
The sample stopped at its first file on Windows: setenv and unsetenv
are POSIX, and the C runtime MinGW builds against has _putenv, where
"NAME=" removes the variable. One helper takes both. Every binary in
the sample also took -DHAVE_MMAP, which to the VFS and memmap means
POSIX mmap and sys/mman.h; without it they take the Win32 mapping
calls, the way the tree's own Windows build does, so the define is
set off Windows only. The windowed-open oracle reads /proc and asks
madvise for huge pages, and is built and run on Linux alone.

With that the decoders, the bench, and the catch-up and blit-band
oracles cross-build as PE, which is where the thread-scaling numbers
have to come from: the hosts that produced the first table had one
core.
2026-09-21 12:40:57 +02:00
libretroadmin a9f8b1cc86 gfx_thumbnail_preview: the preview decoders' throughput, in numbers
Whether a 4K preview is fast enough has been answered by watching it.
preview_decode_bench decodes each file given through the stream the
thumbnail uses, at one, two and four threads, and prints frames per
second, the megapixels per second that implies, and the speed-up over
one thread; with --catchup it runs with the stream told it is behind,
so the gain from dropping droppable pictures sits beside the gain from
threads. `make bench` runs it over the fixtures. It is not a test - it
exits 0 unless a decode fails - and it is never built by check, since
numbers from a sanitized build mean nothing.

On one core, single-threaded: VP9 at 300 Mpix/s, H.264 at 190, HEVC
at 85, against the 250 a 4K stream at 30 fps needs. The thread columns
say nothing on a one-core host and everything on a real one: HEVC's
wavefront rows and VP9's tile columns should scale, and H.264 has no
intra-frame parallelism to scale with.
2026-09-21 12:27:58 +02:00
libretroadmin 3db44ec087 Animated previews: behind the clock, the stream drops what nothing references
A preview whose decode is slower than its frame rate showed every
frame late - a 4K H.264 stream played in slow motion - because the
scheduler noticed it had fallen behind and did nothing about it. It
now tells the stream to catch up, and clears that the moment a frame
lands on time. Catching up means passing over the pictures nothing
predicts from: H.264's non-reference pictures (nal_ref_idc 0), and
HEVC's sub-layer non-reference pictures in the highest sub-layer,
which needs sps_max_sub_layers_minus1 kept from the SPS the parser
read and discarded. Each is consumed without being decoded and the
decode call returns 0, the contract the container already keeps for
a picture held back for reordering, so its presentation slot passes
and the clock catches up. What is still shown decodes exactly as it
would have: a B-frame-heavy 4K stream does about a third of the work
while behind, and no setting, since this only ever engages once late.
WebM, APNG and WebP reference every frame and take no action.

samples/gfx/gfx_thumbnail_preview gains two fixtures with three
non-reference B-frames between references and no pyramid, and an
oracle that decodes each twice: every frame the catch-up run shows
must be a frame of the full run, byte for byte and in order, or a
dropped picture was referenced after all, and it must show fewer.
H.264 shows 18 of 60 and HEVC 17 of 60 - the I- and P-frames exactly.

The oracle that already lived there animated a fixed twelve frames
and asserted a texture had arrived; with two more fixtures ahead of
hevc_wpp.mp4 in the glob, their jobs were still draining when it
opened and under a sanitizer its first picture missed the window, on
this tree and on master alike. The twelve frames stay as the
measurement; the arrival is now waited for.
2026-09-21 12:13:52 +02:00
LibretroAdmin 3d0e133db1 task_queue: push a task's message with property_lock released
The message is built from the task's title, progress and flags under the
lock that guards them, and the push that carries it to the frontend runs
with that lock released. msg_push() inserts into the widget queue and, on
a build with accessibility enabled, reaches a speak backend that forks;
a worker's task_set_progress() has no business waiting behind that.

The push therefore carries the formatted string and nothing the lock
guards, which makes the title and mute tests in
runloop_task_msg_queue_push() the caller's to make: re-reading the title
there would be reading a pointer a worker is free to free, so they go.
The widgets' one remaining read, ALTERNATIVE_LOOK, is set while the task
is built and never changed after, and says so where it is read.
progress_cb() stays under the lock - it reads the task's progress and
finished flag, and pokes the display server rather than going through the
message path.

samples/tasks/progress_push holds the contract. msg_push() there calls
task_get_progress(), which takes that same non-recursive lock, so a push
made under it deadlocks and a watchdog names the phase; the worker churns
the title free-then-set throughout, so a push reading task->title is a
race TSan reports against task_free_title(). Both halves were checked by
injecting each defect. Clean under ASan, ASan+UBSan and TSan; the thread
read audit stays at 0 findings.
2026-09-21 12:11:30 +02:00
LibretroAdmin 262472efa3 samples/gfx: a lane that asserts the filter barrier's contract
filter_lifecycle checks a processed frame's pixels, which reads like
coverage for the fan-out/join and is not: deleting the join outright
leaves it passing, because its slices finish while the verification loop
is still reading them. It also asks for four worker threads and gets
one - Darken and ntsc are the only builtin filters whose
query_num_threads returns the count it was given, every other one pins
filt->threads to 1 - so nothing in samples/gfx has been running the
pool at all.

This lane includes video_filter.c the way samples/audio/mixer_lock
includes audio_driver.c, creates a real pool over Darken, and swaps
filt->impl for slices that sleep on a skewed schedule. On return from
rarch_softfilter_process() it asserts the contract itself: every slice
ran in this generation, outstanding is zero, and no worker still holds a
packet. A watchdog turns a stranded join into a named failure naming the
phase rather than a job that hangs to its limit.

It names four injected defects: the join deleted, the count armed after
the fan-out, a worker that decrements without notifying, and a worker
that tests its flag outside the wait window - that last one a race,
caught on 5 of 5 runs by the no-sleep loop. Clean under ASan, ASan with
UBSan, and TSan. Run by hand; no workflow lists it.
2026-09-21 11:42:16 +02:00
LibretroAdmin faaf0fdec5 video_filter: fan out and join on eventcounts
Each worker parks on an eventcount and takes its packet from a release
store, so the fan-out is a store and a notify per worker with no lock
either side. The join waits on one shared eventcount against an atomic
count armed before any worker is woken, and only the worker that takes
that count to zero notifies, so a frame costs one wakeup rather than one
per thread. Workers test their go flag inside the wait window, which is
what lets a fan-out publishing concurrently release them.

Counted with strace over 1000 frames of the same shape: futex calls drop
from 8919-10574 to 5113-5507 at two threads, 11928-12503 to 7596-7863 at
three, and 12569-14759 to 9460-9856 at four. Wall time is not reported:
on a two-core box the run-to-run spread is wider than the effect.

samples/gfx/filter_lifecycle links retro_eventcount.c, which
video_filter.c now needs.
2026-09-21 11:17:53 +02:00
LibretroAdmin 7269984855 Pace the framebuffer drivers on an eventcount
dispmanx, sunxi and the videocore context carry their pageflip state as
an atomic and wait on retro_eventcount. Each waiter opens its wait
window before the work that a vsync callback answers - the swap, or the
flip test - so a callback landing from there on either shows up in the
re-test or makes the commit return at once, and the frame is never held
for a period it already had. The callbacks themselves now take nothing:
dispmanx's runs inside a VideoCore display callback and sunxi's inside a
thread that should be back in its ioctl.

sunxi's nextPage handoff rides the release store that clears
pageflip_pending, so the page update_main() blits into is published
rather than ordered by coincidence. The videocore context also clears
vsync_callback_set when it stops the callbacks, so raising the swapchain
depth and lowering it again re-arms them instead of leaving a waiter
with nothing to wake it.
2026-09-21 11:01:44 +02:00
LibretroAdmin 1342fdff19 audio_mixer: carry a voice's type as an atomic
A voice publishes its type with a release store when it is claimed and
clears it the same way once released, so the mix loops pass over an idle
voice without taking its lock and a fully idle mixer takes none at all.
Both loops read type again under the lock, where an idle voice lands on
its NONE case, and audio_mixer_play()'s slot search keeps its locked
re-check.

audio_mixer_has_float_voices() and audio_mixer_has_s16_voices() read the
same field, which makes their lockless scans well defined; play_s16()
sets is_s16 ahead of the store that publishes the voice, so those two
see the format flag of any voice they see claimed.
2026-09-21 10:28:17 +02:00
LibretroAdmin d0cb8317dd Take four locks off fast paths
android_display_has_focus() returns one acquire load of an atomic the
focus commands publish with a release store, which is all the ordering
there is to carry; the input poll reaches it every frame. A dispmanx
page carries its used flag as an atomic, claimed by the frame thread and
released in the vsync callback - the form the free-page scan already
reads it in - so a page needs no mutex of its own.

task_queue keeps one lock over the running queue: running_lock guards
every walk and every splice of it, task_queue_remove() included, and
task_queue_push()'s blocking-task scan takes it like the push_running()
that follows. librsound calls the client's audio callback directly;
rsd_callback_lock() and rsd_callback_unlock() go with the lock they
were the only users of.
2026-09-21 10:28:13 +02:00
LibretroAdmin 6f30de6a6f CI: samples/gfx/wayland_drm_lease gets a lane that installs wayland
The sample was running in the leftover job, which installs only
build-essential, so wayland-scanner and the client/server libraries
were missing and the run failed at protocol generation. It belongs in
samples/gfx alongside the other display-server lanes; naming it there
also takes it out of the leftover job, which skips whatever a workflow
claims.
2026-09-21 10:28:09 +02:00
github-actions a8e400bf35 Fetch translations from Crowdin 2026-09-21 00:27:29 +00:00
libretroadmin 0558afa6f3 CI: the Vulkan harness leg holds the surface budgets
No job built the tree with GFX_INSTRUMENT, so the harness lanes that
print what a run cost and hold the budgets - one texture per streaming
surface, no allocation per post, no copy, a 4K submit that stays a
descriptor hand-off - compiled those checks out on every push. The
numbers existed only on whoever ran them by hand. The plain Vulkan
validation leg builds instrumented now and passes the flag to the
harness; it is the one leg for it, since the timings mean nothing
under a sanitizer and the counters change no behaviour.
2026-09-21 00:16:44 +02:00
libretroadmin e3d800c625 Surfaces: the 4K number the plan asked for, and the counters that were missing it
The surface plan holds that nothing lands on architectural
cleanliness alone, and no gate in it had ever been run. The harness
gains a lane that streams a 3840x2160 surface through the wrapper for
sixty frames and takes the numbers: the time a submit costs the main
thread and the frames it takes for the slot to come back, as
p50/p95/p99, with the budgets asserted - one texture, no allocation
per post, no copy, and a submit that stays a descriptor hand-off. It
is thread CPU time, not wall clock: on a one-core host the video
thread's 33 MB upload preempts the main thread inside the submit, and
the clock on the wall charged that to the hand-off.

The counters were missing half the work. A load or update posted
through the asynchronous list never passed through the synchronous
entry point where the counter sat, so every upload the surfaces make
- the point of them - went uncounted, and the baselines printed so
far undercounted by that much. They are counted on the thread that
runs them now. With the true figures the surface lane's own
assertion, two loads for two surfaces, turned out to have passed only
on the undercount: the mode switch between its halves reinits video
and loads the menu's textures through the same counters. Each half is
measured on its own now, with the reinit outside both.
2026-09-21 00:10:37 +02:00
LibretroAdmin 6ef5a6e8f8 CRT: pair the super resolution dropdown's label with its string
Without the row in intl/msg_hash_lbl.h the label resolves to "null",
so the deferred push matches nothing and stays on its default, which
is a file browser: OK on CRT Super Resolution opened Select File
instead of the list.
2026-09-21 00:03:05 +02:00
LibretroAdmin 59ae39cb0f CRT: the refresh-only mode is not about LCDs
What it does is hold the display's own line count and move only the
rate, which fits any fixed-pixel display - an OLED as much as an LCD.
The label named a panel technology instead, so an OLED owner would
read past the one mode that serves them. It now says what it does,
and matches the wording already in the sublabel.

The ini keeps monitor lcd: that is switchres's name for the preset
and it is in people's configuration files.
2026-09-20 23:41:16 +02:00
LibretroAdmin 5a41661b0f Wayland: name the route when a connector is offered
The log said the display server cannot apply a modeline, and
separately which connectors are up for lease, and left the reader to
join them. It now names the setting that takes the offer, and only
where there is one to take.
2026-09-20 23:36:35 +02:00
LibretroAdmin 8582eacdbc Wayland: one place speaks the lease protocol
The display server reported leasable connectors with its own copy of
the listeners, and the lease module then grew the same ones to take a
lease. Reporting moves to the module, on the connection the display
server already has, so the protocol is spoken once - a sequencing
mistake has one place to be made rather than two.

samples/gfx/wayland_drm_lease covers both sides already and is
unchanged, which is what says the reporting still behaves.
2026-09-20 23:34:37 +02:00
LibretroAdmin d8221280b4 Wayland: say when the compositor takes the lease back
A revoked lease reaches the DRM context as a page flip that will not
queue, and looked like any other flip failure. The question is asked
there, where it already went wrong, rather than on every frame: the
lease reads its connection non-blocking - dispatch what is queued,
then read the socket only when it has something - and the context
says so once.

samples/gfx/wayland_drm_lease revokes a live lease from the
compositor's own event loop, since its resources belong to that
thread, and waits on the client side with the same non-blocking call
a caller would use.
2026-09-20 23:32:07 +02:00
LibretroAdmin 373d021c16 thread_read_audit: the Windows entries are reachable, and clean
A mingw cross build puts them in a binary the audit can walk - WASAPI,
WDM-KS, DirectSound, XAudio, ASIO, raw input, the DirectInput
enumeration job, the modeline resync and the D3D11 and D3D12 frame
contexts, none of which a Linux build contains. It audits 62 entries
with no findings, and the note now carries the two commands.

Between that and a wide Linux build's 76, the entries neither can reach
come down from 52 to 27: Apple, Android, emscripten, the consoles, Steam
and a few drivers whose library is not packaged here. Those still want
their own toolchain.
2026-09-20 23:29:02 +02:00
LibretroAdmin 9d3537725c Wayland: scan out on a leased DRM connector
A Wayland client cannot put a modeline on the wire, and a card opened
inside a session cannot modeset because the compositor holds master.
A DRM lease settles both: the compositor gives up a connector, its
CRTC and a plane, and hands back a descriptor that is master for
exactly those, on which an atomic modeset carries a full
drmModeModeInfo.

With that descriptor the existing path serves it unchanged. The DRM
context scans out on it - resources on a lease are only the leased
objects, so the head is already chosen - and the display server is
the KMS one, which applies the engine's modelines to that connector
the way it does on a bare KMS session. A leased connector is one the
compositor has given up, so the desktop keeps the heads it did not
lease: this is a second output, not a takeover.

Reached by setting the video context driver to the DRM one inside a
Wayland session; without a lease that context cannot come up there at
all, so nothing changes for anyone else. The monitor index counts
through the offered connectors as it does elsewhere.

samples/gfx/wayland_drm_lease grants leases now as well as
advertising them, so the request, the descriptor, the index and the
refusal are all exercised against libwayland rather than reasoned
about.

Revocation is not handled: a compositor may take the lease back, and
noticing its finished event needs the connection's descriptor in the
poll the DRM context runs for flip events. Until then a revoked lease
shows up as DRM calls failing.
2026-09-20 23:26:50 +02:00
LibretroAdmin 2db857035c CRT: a dropdown for CRT Super Resolution
Left and right were the only way through NATIVE, DYNAMIC, 1920, 2560
and 3840, so the setting takes OK to a list like the rest of them.

The values are widths, not an index - 0 and 1 mean native and best
fit - which the generic uint dropdown cannot express, since it walks
min to max by step and writes the index back. So the list is its own
DISPLAYLIST_DROPDOWN_LIST_CRT_SUPER_RESOLUTION beside the resolution
one, each row carrying its value, with the entry index only ordering
them. The stored value is unchanged, and left and right still work.

No ST_UI_TYPE: the Qt companion's uint combobox populates from min to
max, which are zero on this row, so the hint would give the desktop
menu an empty box.
2026-09-20 23:14:52 +02:00
LibretroAdmin 25aea68dc4 CRT: the sublabel describes what the setting does
"For CRT displays only" is not true of the engine: the EDID and LCD
modes take their limits from whatever the display reports, and a
panel gets the content's refresh rate the same way a tube does. The
sublabel now says what the setting generates and which modes are for
which display.

English source only; the locales follow through Crowdin.
2026-09-20 23:06:35 +02:00
LibretroAdmin 65f82f7e12 samples/audio: a suite for the PS3 driver's wait and teardown
Two properties with a history, one harness, both against the shipping
translation unit rather than a model of it.

ps3_audio_wait_block() has to hold cond_lock across sysLwCondWait():
that call wants the lwcond's own mutex and fails with EPERM without it,
which is what turned the write's bounded waits into microseconds of
spinning that returned zero and dropped the audio on a full ring. The
device mock refuses on the same precondition and counts it, so the check
is a run of blocking writes longer than the ring: they must all land,
with no refusal counted. Removing the lock again fails at round 16.

quit_thread is written by the thread tearing the driver down and read by
the output thread's loop every pass, with no lock either side. Against
the volatile bool it used to be, ThreadSanitizer reports the read at
ps3_audio.c:89 against the write at :302; it is clean as an atomic.

tools/audio-suite-check.sh finds this for ps3_audio.c on its own, so the
driver's pre-commit check covers it without a workflow change.
2026-09-20 23:04:56 +02:00
LibretroAdmin 64115bd26e CRT: name the display-matching modes, and add refresh-only
"INI" and "EDID" said nothing about what they do, and the one thing
the engine can do for a panel - hold its resolution and move only the
rate - had no entry at all. The dropdown now reads Custom
(switchres.ini), Match Display (EDID) and Match Refresh Only (LCD);
the strings are literals, so no locale is disturbed.

The new mode selects the lcd preset, whose refresh band is seeded
from the display's EDID once the server is bound - its own default is
the desktop rate either side by one, which switches nothing - and the
preset is filled again from that band, since setting the monitor
filled it from the old one. A display that states no band keeps the
desktop rate and says so.

The value is appended, so a saved crt_switch_resolution keeps its
meaning.

samples/gfx/crt_switch_modeline covers it: 70.086 Hz content on a
panel reporting 50-75 Hz lands at [email protected], the panel's own
line count at the content's rate.
2026-09-20 23:04:39 +02:00
LibretroAdmin 4a5863ebb7 samples/audio: a suite for the windowed feeder's dead flag
The flag became an atomic with nothing to reproduce the race, and no
existing suite could: mixer_lock stubs the mixer, and the two mixer
suites under libretro-common are the wrong layer for a tasks file.

audio_mixer destroys a voice from the mixer thread as well as the
caller's, so the borrow's release callback sets dead on one thread while
the feeder task's handler reads it on another, with no lock either side.
This drives that pair directly - including the task's translation unit to
reach them, as samples/audio/psp_ring does for the console drivers, since
both are static. The handler and the release are the shipping ones.

The stubs alongside keep a pass cheap: with no transfer window the
handler bails on its first tick, and the classic loader it falls back to
gets no task, so a round costs the read of dead and little else. The
codec defines matter - without one of them the whole windowed path is
compiled out, and an earlier measurement of this seam was taken against
a build that did not contain it.

Against the volatile bool the field used to be, ThreadSanitizer reports
the read at task_audio_mixer.c:1265 against the write at 1225. Clean as
an atomic, and clean under ASan with leak detection and UBSan.
2026-09-20 22:54:17 +02:00
LibretroAdmin 3958a263ab samples/gfx: pin the EDID preset against the content's refresh
The engine is display-agnostic and the EDID preset takes its limits
from whatever the display reports, so a panel that says it syncs to
75 Hz is handed the content's rate rather than the desktop's. The
case builds a 50-75 Hz block with the tree's own generator, so the
block the parser reads is the one the writer emits, and asks for
70.086 Hz content.

Nothing in the menu says this works on a display that is not a CRT,
which is exactly why it wants a lane: with the display reporting no
EDID the preset falls back to generic_15 and the applied mode is not
at the content's rate.
2026-09-20 22:52:48 +02:00
LibretroAdmin 8bce16cb3c samples/gfx: pin what a rebound display server is handed
Content loading tears the display server down and builds it again
with the engine alive across it. The rebind does not re-enumerate and
an already flushed mode is not flushed again, so the new instance is
asked to switch to a mode it was never handed.

Every backend is fine with that for its own reason - X11 finds the
RRMode still in the X server, KMS reads the timing out of the struct,
the Win32 backends registered it with the driver - and all of them
rely on the mode carrying its whole timing at set(). That is what the
new case pins, together with the switch going through, so a backend
that grew a dependency on its own add() shows up here rather than as
a black screen after a core loads.
2026-09-20 22:46:41 +02:00
LibretroAdmin 90d830f7b1 samples/audio: a lane for the consumer leaving under a parked producer
pipe_consumer_gone became an atomic without anything to reproduce the
race it was: the suite's ThreadSanitizer lanes passed the same either
way, because none of them has the consumer leave its loop while a
producer is still waiting on it.

CONSUMER_EXIT drives exactly that. The producer fills until it has to
wait, the device then refuses every write, and audio_thread_write()
clears alive under thr->lock and the wrapper leaves its loop - reading
alive under the same lock, but publishing pipe_consumer_gone under none,
to a producer reading it under none either. Nothing joins between the
two, because the producer is parked rather than tearing the driver down.

Against the volatile bool the field used to be, ThreadSanitizer reports
the pair: the read in the wait at audio_driver.c:5596 on the submitting
thread against the write in audio_driver_pipeline_consumer_exit() on the
wrapper thread. It is clean as an atomic.

The lane also asserts the producer comes back at all, since a consumer
that has gone is one it must stop waiting for.
2026-09-20 22:44:48 +02:00
LibretroAdmin ede06252c0 Wayland: report the connectors offered for DRM lease
A Wayland client cannot put a modeline on the wire: every modeline op
in dispserv_wl.c is NULL, and the protocol that changes a mode carries
width, height and a refresh rate, nothing that can express a porch.
A DRM lease hands a connector over whole, which is the one route that
can, so the display server now says at init whether the compositor
offers one and which.

Discovery only. The lease is not taken and no scanout changes; what
the log answers is whether a given compositor could ever serve a CRT,
which today nothing tells a user.

The non-master fd the compositor sends for device picking is closed
as it arrives, and the device is released once the connectors are
read.

drm-lease-v1.xml is bundled, and generate_wayland_protos.sh falls
back to the bundled copy per protocol, so a wayland-protocols older
than the one a protocol first shipped in no longer fails the build.

samples/gfx/wayland_drm_lease runs the real display server against a
compositor built on libwayland-server: the mistakes this code can
make are protocol errors, which kill the connection at startup and
are invisible to a compile. Run by hand:

   make -C samples/gfx/wayland_drm_lease check
2026-09-20 22:42:41 +02:00
LibretroAdmin 7fcfa83218 metal: rotate the HDR composite for the slang path too
Under HDR the last slang pass is force-allocated as the composite's
input, so it renders with the unrotated mvp_last_pass like any other
pass that owns an RT. Pass the content rotation to the composite for
that source as well, so TATE content is rotated by the composite's UV
transform whether or not a shader chain is active.
2026-09-20 22:42:25 +02:00
LibretroAdmin 3944cf5b31 CRT: say the context lines once, not on every geometry change
crt_engine_init() runs on every geometry change, so the lines that
describe the context rather than the switch repeat for as long as a
core keeps changing mode. The context identity and the engine-alive
line now belong to the engine's start.

Vulkan direct-to-display says what it cannot do at bind, beside the
no-modeline-path line, which leaves the per-switch branch as the
apply alone.

samples/gfx/crt_switch_modeline covers both contexts over three
geometry changes.
2026-09-20 22:30:52 +02:00
LibretroAdmin 5a4fd86f73 CRT: say when the display server has no modeline path
A server whose modeline ops are all NULL - Wayland - now says so
once, when the engine binds it, naming itself, and the per-switch
apply is skipped rather than reported as a failure. The mode list is
still generated, so the aspect and the timing the consumer publishes
are unchanged.

A server that does have ops and whose set() fails still reports it.

samples/gfx/crt_switch_modeline links the consumer and the engine
and stubs the display server, which is the only way to stand up a
server with no modeline ops on demand. It also pins the connector
the EDID install hint names. Run by hand:

   make -C samples/gfx/crt_switch_modeline check
2026-09-20 22:26:47 +02:00
LibretroAdmin 714d69bf13 CRT: name the selected head in the EDID install hint
The firmware-EDID hint names the connector the monitor index lands
on, resolved the way the Monitor Index setting itself resolves a
head; "auto" is named only where there is a single output, and the
list is read up to eight heads deep.

A second line gives the debugfs edid_override route, which puts the
block in place without a reboot. Not every driver exposes
trigger_hotplug, so the line says so.
2026-09-20 22:15:06 +02:00
LibretroAdmin 50d7e40212 libretro-common: PSP sleeps through its own delay, not newlib's
retro_timers.h includes <pspthreadman.h> for PSP, whose only use in this
header is sceKernelDelayThread, and then named PSP in neither sleep
ladder - so both calls fell through to the generic nanosleep body and the
include was dead. Frame Delay and the runloop's pace to content frame
time are both sub-frame sleeps that go through retro_sleep_us, and on PSP
they were getting libc's resolution rather than the microsecond delay the
kernel offers.

PSP joins VITA, which takes microseconds through the same call with the
same signature. Preprocessing -DPSP now yields sceKernelDelayThread where
it yielded the nanosleep body; GEKKO, WIIU, _3DS, VITA and the generic
branch are unchanged.

The macro also drops a libc symbol where there was one, which the
statically linked platforms care about more than most: nothing in the PSP
salamander object set includes this header, and the one salamander TU
that does is the Wii's, whose branch does not move.
2026-09-20 22:10:13 +02:00
LibretroAdmin da232eb6c0 samples/audio: link audio_upmix into the opensl suite
opensl.c asks audio_layout_channels() which layout the frontend wants,
and the suite builds the driver itself, so its link needs the unit that
answers - the lane fails at link without it.
2026-09-20 21:47:05 +02:00
LibretroAdmin 13239b6f64 microphone: the capture worker reads what wait_readable reports
wait_readable() returns a count, not a yes or no: it is what the device
has now, and that is regularly less than the slice asked for. Every
driver returns short when its bounded wait expires with something in
hand, and alsa returns a period even where the slice spans several -
a small device cannot hold a whole slice at all. read() blocks until it
has everything it was asked for, so the worker must ask only for what
wait_readable() just reported. Otherwise it sits in a read that only
more samples can end, and teardown - which joins this thread
unconditionally - hangs with it, taking mic close, driver switch and
shutdown down too.

The lane goes in samples/audio/mic_handshake, which already drives the
shipping worker against a clocked device. Its read returns what it has,
so it could never show this; the case adds one that blocks as the
header specifies, lets the device go quiet under it, and tears down
with a watchdog so a stuck join fails in five seconds instead of
hanging. The measurement runs keep the non-blocking read and their
numbers.
2026-09-20 21:47:01 +02:00
LibretroAdmin fb3512d39f microphone: drop the read paths nothing can reach
The nonblocking halves of alsa, sdl2, sdl3 and coreaudio's mic reads
went with the hook that selected them, so every read takes the blocking
path the header describes.

That empties the alsa and sdl2 driver contexts: what init() and free()
manage is ALSA's global state and SDL's audio subsystem, and the
frontend only needs a handle to give back, so both hand back the
driver's own vtable rather than an allocation of nothing. CoreAudio's
keeps the microphone it tracks.
2026-09-20 21:36:56 +02:00
LibretroAdmin 662511b338 audio: rate control also requires write_avail
The init gate's comment says rate control needs write_avail and
buffer_size, but it only tested buffer_size. A driver with a size and
no fill took AUDIO_FLAG_CONTROL, and that flag is the only guard on
audio_driver_compute_rate_adjust()'s write_avail() call, so the first
batch past the DRC threshold called through a null pointer. audioio
has exactly that shape on the platforms whose audio_prinfo carries no
seek.

The regression lane goes in samples/audio/pipeline_format, which
already drives audio_driver_init_internal() against a patched
audio_null: rate control must stay off without write_avail and come on
with it. Without the fix it segfaults. That case also needs the
wide-then-float one to release the driver it leaves initialised, which
it now does.
2026-09-20 21:34:06 +02:00
LibretroAdmin b2cc971351 microphone: drop the driver set_nonblock_state hook
Nothing in the frontend has ever called it: audio_driver_set_nonblock_state()
reaches the playback vtable only, and there is no setting, flag or menu
entry behind a non-blocking microphone. The reads the header describes
as the default - blocking until the frontend has what it asked for - are
the only ones a driver is asked for, and the threaded capture worker
wants exactly those, with wait_readable() bounding the wait instead.

Six drivers and the null driver lose the slot, and the three harnesses
that drove it by hand lose the call. The nonblock each driver latches
stays where another path still writes it, so wasapi keeps the state it
takes from audio_sync at init and pipewire keeps what the playback
setter leaves on the shared core.
2026-09-20 21:16:42 +02:00
LibretroAdmin b9da4befff audio: report the room, not a constant, in write_avail
roar and xenon360 both answered write_avail with a literal 0. A driver
that cannot measure its fill should leave the slot NULL rather than
report a constant: NULL disables rate control, while a constant feeds
it a fill that never changes and it corrects against that forever.

switch audout returned the whole size of the buffer it was filling
instead of the room left in it, so a nearly full buffer read as an
empty device. wait_writable in the same file already had the right
expression; write_avail now uses it, and reports nothing when no
buffer is in hand, since acquiring one may block.
2026-09-20 21:05:45 +02:00
LibretroAdmin 513d1b5ed4 audioio: report the room, not the buffer
write_avail() returned buffer_size() - the whole buffer, every call,
whatever was queued in it. Rate control reads that as a device that
never drains and answers with its full upward correction for as long
as it is on, which is the half percent of pitch the PS2 driver carries
a note about having had.

audio(4)'s play.seek is the count of bytes pending, so the room is the
buffer less that. It comes from AUDIO_GETBUFINFO, which exists to be
the cheap read of exactly these fields.

Both are NetBSD's: the Solaris audio_prinfo has buffer_size and
samples and no seek, so there the hook is not offered at all. The
interface asks for nothing rather than a constant, and the frontend
turns rate control off and says so, which is the truth.

buffer_size() returned false from a size_t on a failed ioctl.
2026-09-20 20:54:23 +02:00
LibretroAdmin 4042760eea platform_stubs: an audio(4) header for the BSD audio driver
audio/drivers/audioio.c compiles on the systems whose toolchain ships
<sys/audioio.h> - NetBSD and Solaris - so nothing here has held it to
a syntax gate. A compile-only stand-in, shaped after NetBSD's:

  gcc -fsyntax-only -std=c89 -pedantic -Wall audio/drivers/audioio.c \
      -isystem tools/platform_stubs/bsd -Ilibretro-common/include -I. \
      -DRARCH_INTERNAL

AUDIO_GETBUFINFO is NetBSD's alone, which is what the driver keys its
play.seek branch off; BSDSTUB_NO_GETBUFINFO compiles the other side of
that gate on a host.
2026-09-20 20:54:19 +02:00
LibretroAdmin b71096dc1d samples/audio: a suite for the SDL3 underrun count
sdl3_counters drives the two callbacks directly and then runs the
driver against SDL's dummy device, asserting what the frontend reads:
a covered callback is not a shortfall, microphone puts never reach the
count, feeding leaves it alone, starving moves it, and a reopen keeps
what it has. The shape sdl1_counters uses for the same contract.

Hand-run like the other two sdl3 suites - tools/audio-suite-check.sh
audio/drivers/sdl3_audio.c builds and runs all three, plain and under
ASan+UBSan and TSan.
2026-09-20 20:46:44 +02:00
Rob Loach 232d2604ba SDL3: count underruns on the playback callback only
The output stream gets its own get callback for the count, and the
shared wake goes back to waking. A put callback carries the audio the
microphone just delivered in additional_amount, so counting in the
function both streams call has a working microphone reporting an
underrun per device iteration on its own context.

The count also starts at init rather than at the stream, which is how
audio_driver.h specifies it and how the frontend reads it: the pipeline
compares underruns() against what it last saw and treats any change as
fresh silence, so a count reset by a device reopen costs it a pass and
the audio it was holding.
2026-09-20 20:46:40 +02:00
LibretroAdmin 246f41cef6 audioworklet: count the silent quanta instead of printing one
The process callback fills a quantum with silence in two places: when
the ring comes up short, which it already computes zero_frames for,
and when it cannot take the lock at all - and that one printed a
warning from the render callback and returned. Both are one atomic add
now, and underruns() hands the count to the frontend, which shows it
and says it once at teardown.

The short-ring case counts only while the driver is running: one that
is not running is not being starved.

memalign() reached this file through <memory.h>, which emscripten's
own webaudio.h pulls in; <malloc.h> is included here now, so the
driver stands on its own.
2026-09-20 20:42:07 +02:00
LibretroAdmin 3130d7d3d6 platform_stubs: emscripten headers for the audio worklet driver
audio/drivers/audioworklet.c compiles only under emscripten, whose
headers refuse to be included from its source tree - they want a
generated sysroot - so nothing here has held that driver to a syntax
gate. Stand-ins for <emscripten/webaudio.h>, <emscripten/wasm_worker.h>
and <emscripten/atomic.h>, and the EM_ASM_* macros beside the
emscripten.h stub that was already here:

  gcc -fsyntax-only -std=gnu99 -Wall audio/drivers/audioworklet.c \
      -isystem tools/platform_stubs/emscripten \
      -Ilibretro-common/include -I. -D__EMSCRIPTEN__ -DRARCH_INTERNAL

The webaudio stub includes emscripten.h as emscripten's own does, so
the driver reaches the EM_ASM_* macros the way it does in a real
build. Struct shapes follow emscripten's, because the driver
initialises the options positionally and indexes the sample frames.
2026-09-20 20:42:04 +02:00
LibretroAdmin 4b22f89e7a alsa_qsa: count the underrun instead of logging it
check_pcm_status() runs from the write path and said SND_PCM_STATUS_
UNDERRUN to the log every time the device ran dry - a line during
play, on the path whose margin it is reporting on. It is one atomic
add now, and underruns() hands the count to the frontend, which shows
it on the statistics overlay and says it once at teardown.

The file also reached memset, calloc and the errno values through
QNX's <sys/asoundlib.h>, which pulls them in; they are included here
now, so the driver stands on its own includes.
2026-09-20 20:35:18 +02:00
LibretroAdmin 87d3596639 platform_stubs: a QSA header for the QNX audio driver
audio/drivers/alsa_qsa.c compiles nowhere but QNX, whose headers ship
with its toolchain, so nothing in this tree has ever held it to a
syntax gate. A compile-only stand-in for <sys/asoundlib.h> is enough
to, as the android and ps3 stubs do for theirs:

  gcc -fsyntax-only -std=c89 -pedantic -Wall audio/drivers/alsa_qsa.c \
      -isystem tools/platform_stubs/qnx -Ilibretro-common/include -I. \
      -D__QNX__ -DRARCH_INTERNAL

Shapes and spellings follow QSA rather than ALSA; the values are
arbitrary where the driver only assigns or compares them.
2026-09-20 20:35:15 +02:00
LibretroAdmin 1462d76b7a samples/audio: a suite for the SDL3 capture wait
sdl3_mic_wait drives the put callback by hand against a real unbound SDL
stream and asserts what the capture worker depends on: a silent device
answers within one device period, a period satisfies a slice-sized
request, a put ends a parked wait, and an unplug ends one with nothing.
Hand-run like sdl3_raw beside it - tools/audio-suite-check.sh
audio/drivers/sdl3_audio.c builds and runs both, plain and under
ASan+UBSan and TSan.
2026-09-20 20:37:27 +02:00
LibretroAdmin f83c61a68f SDL3: bound the capture wait by the recording device's period
sdl3_microphone_wait_readable() takes its per-wait bound from
sdl3_microphone_wait_ms() - two device periods, clamped to 20-200 ms,
the shape alsa_microphone_wait_ms() uses - and caps what it waits for at
one period. The capture worker re-reads its exit flag only between these
waits, so that bound is also how long closing a microphone holds the
main thread in sthread_join(); and a target above what buffer_size lets
the stream hold can never be reached, the put callback dropping the
backlog past it. The read loop's wait takes the same bound, and
period_frames carries what SDL reports at open for all three streams.
2026-09-20 20:37:27 +02:00
Rob Loach 3f5f41f56a SDL3: mic wait parks on put callback 2026-09-20 20:37:27 +02:00
LibretroAdmin 0c5a699107 opensl: open the layout the frontend asks for
The player was created with two channels whatever the setting said.
The PCM_EX format the float path already uses carries a channel mask
as well, and the frontend's mask is that mask - both are the
WAVEFORMATEXTENSIBLE bits - so it goes across as it is and the frames
arrive in its ascending-bit order either way.

Tried once, ahead of the stereo attempts, and only where PCM_EX is:
API level 21, the gate the float path is already behind. A player that
refuses the wider format falls through to those attempts untouched,
which is also what an older API level does, and layout() then reports
stereo.
2026-09-20 20:28:29 +02:00
LibretroAdmin ebd9f766f5 audren: open the layout the frontend asks for
The renderer was built for two channels whatever the setting said.
audrvDeviceSinkAdd() takes up to six, so 5.1 is the widest this
renderer has, and it now opens that when the layout asks for it. The
mix buffer count follows the channel count, which is why the config
moves into the driver: audrenInitialize() and audrvCreate() have to be
handed the same one. Anything that is not two or six - a four channel
mask - opens as stereo, and layout() reports what was granted.

use_float() says why it is false while it is here. audrvVoiceInit()
takes PcmFormat_Int16 and PcmFormat_Adpcm and rejects the rest,
PcmFormat_Float among them, so this is libnx's limit and not a
setting this driver declined to read.
2026-09-20 20:23:34 +02:00
LibretroAdmin 330b083cfe ps3: open the layout the frontend asks for
The port was opened with two channels whatever the setting said.
libaudio takes six and eight as well, so it now opens what the layout
asks for and falls back to two when the port refuses it or the count
is not one libaudio has - a four channel mask is not. layout() reports
what was granted, so the frontend upmixes to that and no further.

The block the output thread hands to audioAddData() moves to the heap
on the way. That thread is created with 4 KiB of stack, which one
stereo block already half fills; six channels would have run straight
past the end of it.
2026-09-20 20:18:35 +02:00
LibretroAdmin 6feff4679c gx, ctr_dsp: take the counter from libretro-common
Both clock fits read the platform's tick register directly -
gettime() on the one, svcGetSystemTick() on the other.
cpu_features_get_perf_counter() is those two calls, on those two
platforms, and is where a counter read belongs.

The scale stays per-platform: features_cpu.h offers the counter and
no frequency beside it.
2026-09-20 20:18:31 +02:00
LibretroAdmin e1d848bd71 gx, ctr_dsp: report the device clock
Both know the device's position - the DMA callback counts chunks, the
DSP callback reads the channel's sample cursor - and neither took a
timestamp beside it. With one, the slope of position against time is
what the hardware is really running at, against the rate the driver
announced.

The AI's is not a crystal difference but a divisor: it divides its
source clock by a figure that does not land on the rate it is asked
for, and the gap has never been reported anywhere. The DSP's is a
straight comparison of two domains, the channel's cursor against
svcGetSystemTick().

gx fits on the frontend's thread from a pair the callback publishes
under a sequence counter: that callback is a DMA interrupt, and
floating point does not belong in one. The pair has to come from
there even so - sampling the position from the frontend is a chunk
out, which over any usable window is more than the figure being
measured. ctr_dsp fits in its callback, which is an ordinary thread,
and publishes the result as one int.

ctr_dsp takes svcGetSystemTick() rather than
cpu_features_get_time_usec(), which is osGetTime() on this platform
and resolves in milliseconds - a hundred parts per million of
quantisation on a figure measured in single parts.

Measurements, both. Nothing acts on either.
2026-09-20 20:10:50 +02:00
LibretroAdmin 5c851eef6d ps2: report what audsrv knows, and wait for it with an end
The driver handed audio to audsrv and asked it nothing back, so it had
no write_avail() and rate control stayed off for the session. audsrv
answers all three questions: audsrv_available() is the room in its
ring, audsrv_queued() is what it still holds, and the ring's size is
what it reports free before anything is queued - so buffer_size()
becomes the real ring rather than a constant, write_avail() a
measurement, and frames_consumed() what was handed over less what is
waiting.

wait_writable() polls that room with a bounded sleep and a lap cap
rather than calling audsrv_wait_audio(), which blocks with no bound of
its own and would park the audio thread on a server that stopped
draining.

nonblock is still not implemented; the frontend's own policy now has
the room figure to work from.
2026-09-20 19:59:33 +02:00
LibretroAdmin b72e9d374d pulse: report the sink's clock
The latency-update callback already holds the server's timing info and
read only sink_usec from it. read_index is what the sink has played,
in bytes, and timestamp is when that was true - the pairing of
position and time device_clock_ppm asks for, arriving on its own
without a call to fetch it.

Fitted over the window rather than between two points, and restarted
when read_index steps back on a rewind or a flush. A measurement:
nothing acts on it.
2026-09-20 19:33:43 +02:00
LibretroAdmin eec1a9dbe2 tinyalsa, rsound, sdl3, opensl, ps3, gx, wiiu: count the silence they already see
The rest of the drivers that detect a starved device and do nothing
with it. tinyalsa recovers from EPIPE; rsound's callback returns a
short count librsound zero-fills; SDL3 is handed the shortfall as the
get-callback's additional_amount and ignores both its integer
arguments; OpenSL's buffer-queue callback decrements to zero and drops
the result; the PS3 output thread memsets a block it had nothing for;
the GX DMA callback retires each chunk by erasing it, so a chunk the
writer has not reached goes out as the silence left behind; the AX
frame callback already stops the voice when the buffer is short.

Each adds one to a counter where it happens and reports it through
underruns(). GX and AX use the plain volatile counters beside them
rather than atomics, as those files do for their frame counts.

rsound, ps3, gx and opensl stopped short of the slot, so the members
before it are named NULL rather than left to position.
2026-09-20 19:33:39 +02:00
LibretroAdmin d12c42ffd0 ctr_dsp: one rate for the channel and the frontend
init reported 32730 Hz to the frontend and set the channel to
32728 Hz. The channel interpolates with NDSP_INTERP_NONE, so the
frontend resampled to a rate the DSP was not playing at and the ~60
ppm between them had nothing to correct it. Both now come from one
constant.

Also drops the second `if (!ctr)`: ctr is checked, with ndspExit(),
where it is allocated, and the callback and the event have already
been given it by the time the second check is reached.
2026-09-20 19:33:35 +02:00
LibretroAdmin b8e4e9d0de rsound: include verbosity.h for the log it makes
rs_init() calls RARCH_LOG without the header that declares it, so a
build with HAVE_RSOUND=1 stopped on an implicit declaration under
C89. It reached the file transitively on no include path this tree
uses.
2026-09-20 19:33:32 +02:00
LibretroAdmin 5b10ad98d0 thread_read_audit: the coverage note counts the audio callbacks now
The device callbacks, with JACK, PipeWire and PulseAudio built, take a
wide Linux build from 59 audited entries to 76, all clean. The note's job
is to stop a narrow build's zero being read as a clean tree, so the
figure in it should be the one a wide build actually reaches.
2026-09-20 19:18:50 +02:00
LibretroAdmin 7d1185f2d9 overlay_on_screen_test: look at the screen once it holds still
The grab waits for two polls to agree on a screen something has been
drawn to, up to a deadline, instead of trusting a fixed wait. A
frontend that is still starting has drawn nothing, or has drawn into a
window short of its final size, and either is a picture of the startup
rather than of the overlay.

A wrong picture settles at once and fails as it always did: a driver
squeezing the quads is caught on the first pair of polls, and an
overlay that never arrives fails on the menu screen beneath it. A run
that stays in motion to the deadline says so and is checked anyway. On
a healthy machine both lanes now finish sooner than the old wait alone
took.
2026-09-20 19:09:48 +02:00
LibretroAdmin c576919a1f alsa, pulse, pipewire, dsound, openal: count the silence they already see
Each of these drivers already knows when the device went without
audio and did nothing with it. ALSA recovers from -EPIPE and drops the
fact; PulseAudio registers an underflow callback whose body is #if 0'd
around a log line; PipeWire fills the quantum with silence when the
ring is empty; DirectSound's mixer thread has a branch commented
"nothing in FIFO (underrun)"; OpenAL finds the source STOPPED and
restarts it. All five now add one to a counter where it happens and
report it through underruns().

The frontend shows the count on the statistics overlay and says it
once at teardown, which is where a line about audio belongs - pulse's
disabled callback was disabled because it logged from the mainloop's
thread while audio played.

Only the event each backend really reports: -EPIPE and not the -EINTR
and -ESTRPIPE beside it, a PipeWire quantum with nothing at all rather
than a short one, and AL_STOPPED rather than any state that is not
AL_PLAYING, so the first write of a session does not count itself.
2026-09-20 19:06:56 +02:00
LibretroAdmin 3c63e45d8b thread_read_audit: audit the audio device callbacks
The gate walked from the threads this tree creates and from the audio
pipeline's callback. A driver's device callback belongs to the backend
- SDL's device thread, libpulse's and PipeWire's loop, JACK's process
thread, the ASIO driver thread, CoreAudio's listeners, the DSP and AX
frame interrupts - so no create call here names it and the binary walk
has no edge to reach it. Those callbacks are worker threads by every
other measure, and nothing was auditing them.

They are declared, for the same reason the driver frame-context
functions are. A driver's init, open and free stay off the list: they
run on the main thread, where a settings read is legal and every one
of them would be a false finding.
2026-09-20 19:06:52 +02:00
LibretroAdmin 69bdd44f93 asio, jack, coreaudio: take the reinit latch on the main thread
The device callbacks reached for audio_state_get_ptr() to raise
reinit_request: the ASIO driver thread on a reset or rate change,
JACK's server threads on a rate or period change, the HAL's listener
when the default output moves. The singleton is static and the field
is an atomic, so nothing was unsafe, but a worker reaching into
frontend state is the shape the thread-read audit exists to reject,
and wasapi already avoids it - it captures the same pointer on the
main thread at sthread_create and hands it to the worker.

Each driver now takes the pointer at init and its callbacks store
through it.
2026-09-20 19:06:46 +02:00
LibretroAdmin 4bfeb65066 audren, ctr_dsp: bound the waits on the device's frame event
Both waited without a timeout. audrenWaitFrame() is eventWait() with
UINT64_MAX, and LightEvent_Wait() has no bound at all, so a renderer
or a DSP that stops signalling - stopped, reset, or the channel
dropped coming back from sleep - parks the caller for good. On
wait_writable that is the threaded pipeline's audio thread; audren's
blocking write could hold the core's thread the same way.

Both now take the event with a timeout and a lap cap, as every other
driver's wait does, and report a short count when it expires. The
timeout is never reached while the device runs, so it only decides how
long a stopped one takes to be noticed.
2026-09-20 19:06:43 +02:00
LibretroAdmin 0d7ae0dc9d openal: resolve the device clock through a union
Casting alcGetProcAddress()'s object pointer straight to a function
pointer is not legal C, and `make C89_BUILD=1` with HAVE_AL=1 stopped
on it. al_init_events() a hundred lines above already resolves its two
entry points through a union; the device clock now does the same.
2026-09-20 19:06:34 +02:00
LibretroAdmin 46d69f2c81 (Wii/Wii U) Atomics for the HID poll threads' quit flags
Both drivers park a poll thread on a flag the teardown sets from another
thread, with no lock either side, as volatile bools that ordered nothing
between the two. The flags are initialised before their thread is
created, and on the Wii USB side before the first goto error, since free
stores through it.

wiiusb_hid's device_detected goes the other way. It is raised by
wiiusb_hid_change_cb, which libogc runs in interrupt context, so an
atomic is not what it wants - the writer is not a thread, and this file's
other interrupt-shared state is volatile for that reason. It was a plain
bool, which left the poll thread free to hold it in a register across the
loop; volatile is the smallest thing that fixes that.

Both files build against the headers already in the tree - libogc under
wii/, wut under wiiu/ - on a 32-bit PowerPC compiler and on all five
retro_atomic backends, unmodified first and then with the change.
2026-09-20 19:06:30 +02:00
LibretroAdmin 7752034268 (Android) Add a harness for the deferred keypress haptic
samples/input/android_haptic_defer reproduces the defer protocol and
pins it: the callback never enters Java, repeats inside one iteration
coalesce, a press is not carried across a pause, stop, window teardown,
focus loss or destroy, and the delivery guards catch a press raised
after the command in the same poll batch. Two sabotage modes put the
pre-defer dispatch and the missing clears back and are asserted to be
caught. Run by hand, like samples/input/android_kbd_mailbox.
2026-09-20 19:08:17 +02:00
LibretroAdmin f6e7ece621 (Android) Post keypress haptic feedback to the UI thread
doHapticFeedback() is called from the native app thread, and
View.performHapticFeedback() is not documented as thread safe. Drop the
per-keypress log line with it: it runs on an interactive path.
2026-09-20 19:08:17 +02:00
LibretroAdmin bb478c5935 (Android) Keep the pending-haptic flag with the driver's other deferred state
The flag is input-driver state, set and read only on the app thread.
Hold it as a file static beside android_state_flush_pending rather than
in struct android_app, which is shared with the Java thread under the
app mutex. Clearing it before the delivery guards also stops a request
raised before g_android exists from sticking.
2026-09-20 19:08:17 +02:00
LibretroAdmin 02c79d12a2 (Android) Flush pending haptics from the runloop's deferred-work slot
runloop_iterate() already carries the Android work that must happen
outside the core, next to the APP_CMD_PAUSE save that defers for the
same reason. Dispatch the keypress haptic there instead of from
retroarch.c's outer loop, which is compiled out under HAVE_MAIN.
2026-09-20 19:08:17 +02:00
Starlightbotanist be6d649385 Android: defer keypress haptics to the outer app loop
Record pending keypress feedback in Android app state and dispatch it after runloop_iterate() returns. Coalesce requests per iteration and clear pending feedback on pause, stop, focus loss, window removal, and destruction.

Guard delivery by activity, window, focus, and JNI method availability.
2026-09-20 19:08:17 +02:00
LibretroAdmin c9e5688057 overlay_on_screen_test: a failure says where the overlay actually is
A failing lane prints the rectangles on the screen grab, the
rectangles the config asks for, and the lines of the log that say
which context, driver and resolution drew them. A geometry failure is
then read off the CI output rather than reproduced: two bands of two
spans are the two squares, and their rows and columns say what moved
them. A passing lane prints the line it always did.
2026-09-20 18:53:58 +02:00
LibretroAdmin 3c639f4f15 (PS3) An atomic for quit_thread, which crosses two threads
Set by the thread tearing the driver down and read by the output
thread's loop on every pass, as a volatile bool that ordered nothing
between them. The same flag is already an atomic in the PSP, Vita and
PS4 drivers; this one was left behind.

tools/platform_stubs/ps3 grows enough of PSL1GHT's audio, thread,
event-queue, mutex and cond headers to compile the driver on a host, so
the change is checked rather than read: the driver builds unmodified
against them first, then on all five retro_atomic backends. The headers
ps3_defines.h includes unconditionally are there as empty placeholders,
marked as such.
2026-09-20 18:49:18 +02:00
LibretroAdmin 44c99646f8 libretro-common: retro_atomic.h needs boolean.h for its own fallback
The volatile fallback backend declares retro_atomic_cas_int_fb as
returning bool, the header's only use of the type, and nothing here
provided it. A translation unit that includes retro_atomic.h before
anything else fails to compile on that backend - two lines are enough to
show it - which is every platform without real atomics.

The forced-backend loop in the gfx samples workflow did not catch it
because all four files it compiles reach bool through another header
first. boolean.h is what the sibling headers use, is header-only so the
salamander object sets are unaffected, and is a no-op under C++, where
this header is included inside extern "C".
2026-09-20 18:49:14 +02:00
LibretroAdmin d451414e61 ctx_presentable_check: the checks read code, not comments
The vtable scan, the shared-declaration scan and the driver count run
over a copy with comments and literal contents blanked, so a slot
spelled `NULL, /* presentable */` - the tree's way of naming every
unimplemented slot - is a NULL slot and nothing more. Blanking keeps
every offset, so the brace walks still line up with the text.

Three selftest fixtures hold it: a NULL slot named in a comment, a
comment beside a wired hook naming a different one, and the hook's
name inside the driver ident string. Each of the three reports the
checker's defined-nowhere error without this change.
2026-09-20 18:43:22 +02:00
LibretroAdmin fbdd2c2cae audio: atomics for the two cross-thread flags that were volatile bools
Both are written by one thread and read by another, and volatile orders
nothing between them, so neither reader had a guarantee of seeing the
write at all. This is the same reason capture_running in
microphone_driver.h is an atomic, where ThreadSanitizer reported the
race on the field itself.

pipe_consumer_gone is set by the wrapper thread as it leaves its loop
and read by the producer deciding whether to keep waiting; it takes a
release store and an acquire load.

audio_mixer_wfeed's dead is set by the sound's release, which
audio_mixer runs from the mixer thread as well as the caller's, and read
by the feeder task's handler on the task thread.

Checked on all five retro_atomic backends, C11 through the volatile
fallback, and on a clean C89 build.
2026-09-20 18:39:16 +02:00
libretroadmin dca7a0527f Cleanups 2026-09-20 18:37:33 +02:00
LibretroAdmin c202066f26 gfx: split sdl_gl_ctx.c into sdl1_gl_ctx.c and sdl2_gl_ctx.c
Each unit targets a single SDL major version and exports its own vtable:
gfx_ctx_sdl1_gl and gfx_ctx_sdl2_gl. Makefile.common selects the object
inside the existing HAVE_GL_CONTEXT guard, griffin includes it in the
matching arm of its SDL chain, and gfx_ctx_gl_drivers[] lists whichever
one the build defines - SDL2 first, as the merged file's own #ifdef chain
resolved it. Both keep the "gl_sdl" ident, so video_context_driver settings
carry over untouched.

sdl1_gl_ctx.c holds the SDL 1.2 surface path alone: no GL context or shared
context members, no webOS hints, and no settings lookups, since the monitor
index and windowed-fullscreen flag only ever fed SDL_CreateWindow. Its
presentable slot is NULL, which gfx_ctx_driver_t documents as "always
presentable"; bind_hw_render stays an empty function rather than NULL
because gl3.c calls it unguarded whenever a core requests hardware
rendering.
2026-09-20 18:31:54 +02:00
LibretroAdmin c44adcd1ba input: split sdl_joypad.c into sdl1_joypad.c and sdl2_joypad.c
Each unit targets a single SDL major version and exports its own vtable:
sdl1_joypad (ident "sdl") and sdl2_joypad (ident "sdl2"). Makefile.common
selects the object alongside the matching input driver on HAVE_SDL2,
griffin includes it in the same arm, and joypad_drivers[] lists whichever
one the build defines.

sdl1_joypad.c keeps only the plain joystick API: no GameController, haptic
or sensor members, so the pad accessors collapse into their call sites and
set_rumble, set_sensor_state and get_sensor_input are NULL entries rather
than stubs that always return false - both are null-checked by their
callers. The unused compat/strl.h include goes with it.
2026-09-20 18:05:07 +02:00
libretroadmin 36b9cb7436 sdl_dingux_joypad - cleanups 2026-09-20 17:56:40 +02:00
LibretroAdmin a513a10403 input/sdl2: report the horizontal mouse wheel
The wheel event already records mouse_wl/mouse_wr; hand them back under
RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELUP/DOWN so cores and the menu see tilt
scrolling, with the same axis orientation sdl3_input.c uses.
2026-09-20 17:51:11 +02:00
LibretroAdmin 78c8fd23e0 input: split sdl_input.c into sdl1_input.c and sdl2_input.c
Each unit targets a single SDL major version and exports its own vtable:
input_sdl1 (ident "sdl") and input_sdl2 (ident "sdl2"). Makefile.common
selects the object on HAVE_SDL2, griffin includes the matching unit in
each arm of its SDL chain, and the driver registry lists whichever one
the build defines. The SDL1 arm of griffin now carries the input driver
and joypad next to the audio driver so it links on its own.

sdl1_input.c omits the KMOD_SCROLL probe: 0x8000 is KMOD_RESERVED under
SDL 1.2, so scroll lock stays unreported there and remains SDL2-only.
2026-09-20 17:51:07 +02:00
LibretroAdmin 016966b9c8 tools: sanction the frame roots' display reads in the thread audit
A build with Vulkan, Qt, ffmpeg, HID and Bluetooth audits 59 worker
entries where a default Linux build audits about 42, and the wider call
graph reaches five gfx_display helpers from gl2_frame, gl3_frame and
vulkan_frame. They carry the same argument as the pairs already
sanctioned through the menu and widgets stack: past gfx_display_flush_as
they touch only blend_on and dispctx. Keyed on the reading function, so
one line each covers all three roots.

video_thread_hw_allowed() goes in with its reasoning written out. The
core's context type and the configured driver name are both settled
before the video thread exists, and a driver change reinits through a
join. The bounded transient between the menu's write and that reinit is
recorded rather than papered over, together with why latching is not the
cheaper answer: the macro calling it runs inside gl2 and gl3 init.

The count itself is worth a note, since an entry absent from the binary
is not a finding and a narrow build reporting none is not a clean tree.

Checked by injecting an unsanctioned reader into one of the five
functions: the pairs are per reader, so the audit still reports it.
2026-09-20 17:50:47 +02:00
LibretroAdmin d35bb7777e sdl2_audio: open the device the user picked, and list capture devices
The Device setting reached both init paths and neither used it, so the
list the menu offered could be browsed but not applied. Both now open
the device by name and fall back to the default with a warning when
the name has gone with the hardware it named, rather than failing init
on a stale setting.

Enumeration no longer assumes SDL_INIT_AUDIO is already up: the
frontend builds the list whether or not the driver came up, which is
how the user picks a different device after the current one failed.
The microphone driver gains the same list; it had none.
2026-09-20 17:43:40 +02:00
LibretroAdmin d9cfd3813f sdl1_audio: order the driver's setup before the device thread sees it
SDL 1.2 opens the device with its audio thread already running and
unpauses it by writing a plain flag, so the ring and the park built
between those two points reach that thread with nothing ordering them:
a period that observes the device unpaused may observe a ring that is
not yet built. Taking SDL's audio lock, which that thread holds around
every period, across the unpause is the edge. SDL2 locks and uses an
atomic for this itself.

The handhelds this driver serves are MIPS and ARM, where the stores
are free to arrive in the order the race allows.

samples/audio/sdl1_counters covers this and the driver's device
accounting: SANITIZER=thread reports the race when the lock is taken
back out. Run by hand, as the sdl_lost_wakeup suite is.
2026-09-20 17:43:36 +02:00
LibretroAdmin c3d201d868 sdl1_audio, sdl2_audio: report device time and silence
Both drivers run on an SDL callback that is the device asking for one
period, so counting what it asks for counts device time, and the short
read it zero-fills is a period of silence for want of audio. Bytes and
periods are counted on that path with one atomic add each and turned
into the frontend's frames_consumed() and underruns() off it.

frames_consumed() gives the frontend the device's real rate against
the host clock, so the resampling ratio can be trimmed by the
difference between two crystals; underruns() feeds the statistics
overlay and the one line the frontend logs at teardown.
2026-09-20 17:43:33 +02:00
LibretroAdmin 9e36f9fca6 rthreads: share the false return in the priority and affinity setters
sthread_raise_current_priority and sthread_set_affinity return false
both from the guard ahead of the call they make and from the #else arm
for a platform with neither. Inverting each guard lets the arm fall
through to a single return after #endif.
2026-09-20 17:43:11 +02:00
LibretroAdmin bfc0b9abbd (PSP/Vita/PS4) Bound the starvation lane against the ring geometry
The 30 Hz lanes sat one period inside their own tolerance. A delivery
needs RING_FREE >= its own size, so at the 2560-frame floor a
1600-frame delivery waits until held <= 959; draining a period at a
time from the previous refill, the values the writer is released at
cycle through 448, 512 ... 896, and the first of those eight is below a
period. One refill in eight has to let the ring dip under a period
before it can be refilled, which is 24 of 600 periods - the 4% the
floor was chosen against, and exact rather than noisy. The bound was
5%, so ordinary jitter on top of a fixed 24 reached 29 and 30 against a
limit of 30. It is now twice the geometric figure, which still fails a
four-period floor and a worker that holds a period back as a reserve;
both were re-checked by injection.

The writer's usleep pacing goes with it. Rate-matched to the device by
construction - 1600 frames per 3125 us against 512 per 1000 us - and
usleep only ever oversleeps, so the ring level could only walk
downwards. It made no difference to the counts either way, because the
refill point is set by the ring geometry and not by the writer's rate,
and the writer already self-paces by blocking for room.

A six-period floor would not dip at all; the released values start at
960. That is a latency decision rather than this lane's to make.
2026-09-20 17:36:53 +02:00
LibretroAdmin 26ebbf70d7 gfx/drivers: delete unused psp1_gfx.h
Nothing in the tree includes it, and every type, extern and macro it
declares is absent everywhere else, including in psp1_gfx.c next to it.
Its "pspgu.h" include is unresolvable from gfx/drivers, so it cannot
compile where it sits.
2026-09-20 17:35:53 +02:00
LibretroAdmin 674cf0d693 samples: stop tracking the generated wasapi_sim.c
Both pacing suites generate wasapi_sim.c from audio/drivers/wasapi.c
with sed. The committed copies had drifted from the driver, and since
make regenerates only when the driver is newer, a fresh clone could
build either text depending on checkout timestamps. Generating it
always is what the suites mean by the driver's text.
2026-09-20 17:29:10 +02:00
LibretroAdmin 9fe27f8da3 Remove break statements that follow an unconditional return
Thirteen switch cases end in a return or a goto and then a break the
case can never reach. Object code is unchanged.
2026-09-20 17:29:07 +02:00
libretroadmin 690687bb60 Another unnecessary comment 2026-09-20 17:25:33 +02:00
libretroadmin ee9e0b5dbc Unnecessary comment 2026-09-20 17:24:58 +02:00
LibretroAdmin 261f3028cc apple: make the iOS and tvOS deployment targets overridable
RA_IPHONEOS_DEPLOYMENT_TARGET and RA_TVOS_DEPLOYMENT_TARGET set the floor
for every target in RetroArch_iOS13.xcodeproj, so a runner on an Xcode that
has raised its minimum can build the store packages without a tree edit.
Unset, the targets keep the values they build with today.
2026-09-20 17:21:26 +02:00
LibretroAdmin 250584f48c apple: raise Mac App Store deployment target to macOS 12.0
Xcode 27 accepts macOS deployment targets in the 12.0 - 27.0.x range, so
the store package targets 12.0. RA_MACOSX_DEPLOYMENT_TARGET overrides it
for builds on machines carrying an older Xcode.
2026-09-20 17:19:41 +02:00
LibretroAdmin 4ff07a1798 memmap: bounded name formatting in the area sample
The Windows job builds this with cl /W3 /WX, where sprintf raises C4996
and C2220 turns it into an error, so that lane has never been green.
snprintf takes the buffer size and MSVC does not deprecate it; the
lane's other translation unit is already clean.
2026-09-20 17:16:08 +02:00
LibretroAdmin 060ff93b15 cheevos: drop the blank lines at the end of cheevos_menu.c 2026-09-20 17:10:01 +02:00
LibretroAdmin d4293fb038 (PSP/Vita/PS4) Hold the audio drivers to the contracts they assume
Two things the three drivers took on trust.

A worker that fails to start is not an initialisation failure: running
is set before the create, so the driver comes back reporting itself
alive with nothing consuming its ring. Writes then wait out their laps
and return zero, and free() skips the stop because it is nested under
the thread pointer. init now releases the port, the ring and the
eventcount and reports none, and start() puts running back and answers
false, which is the same hole on resume.

write() derived its frame count as uint16_t from a byte length. Above
65535 frames the count wrapped small, the room check waved it through
on the space the wrapped value asked for, and the non-wrapping memcpy
then copied the original byte length - 262144 bytes into a 12 KiB ring
under the harness. The frontend's output scratch is sized at
max_buffer_samples * AUDIO_MAX_RATIO * slowmotion_ratio and written in
one call, so the length is reachable from an extreme resampler ratio.
The count is a size_t, a length that is not whole frames carries the
frames it has, and anything past what the ring holds is taken short
rather than waiting out 800 ms for room that cannot come.

psp_ring grows a lane for each, over all three drivers: device_mock
supplies the drivers' sthread_create through a rename so one can be
refused, and the write lane walks 0, 1, 65535, 65536, 65537 and 131072
frames plus a six-byte write. Against the previous code the first lane
reports FAIL with alive=1 on all three and the second is an ASan
heap-buffer-overflow of 262144 bytes in psp_audio_write.
2026-09-20 17:09:58 +02:00
LibretroAdmin 335b027672 memmap: hold memshm_area_map/unmap to the reservation they advertise
The contract says @at lies within the reservation, and neither function
checked it. On POSIX the map is MAP_FIXED, so an address outside the
area does not fail - it replaces whatever is mapped there. The offset
was also taken by subtracting base from an address not yet known to
belong to the area, the legacy slot check could overflow in both the
rounding and the addition, and the placeholder path confirmed only that
the first byte fell in a free range, so a length reaching past the end
recorded a range running backwards.

memshm_area_offset carries both arguments as uintptr_t and orders the
comparisons so neither the addition nor the subtraction can wrap; the
POSIX paths round the length up to a page first, because that is what
mmap replaces. unmap refuses an unbalanced call rather than wrapping the
mapping count.

On Windows the range table was also edited before the OS calls that
make the split, and a failure returned without putting it back, leaving
the software map and the VADs describing different things. Each step now
calls the OS first and records the split only once it succeeds, and a
table too full to record the result is refused before anything is
touched.

The test grows negative lanes for each of these: an address below the
base, the first address past the end, a length crossing it, a length
that would wrap, and an unbalanced unmap - plus a mapping made outside
the area, checked byte-wise to still be there afterwards. Against the
previous code the first two report FAIL and the third takes a bus error.
2026-09-20 17:09:54 +02:00
libretroadmin 175bbfded5 Cleanups 2026-09-20 17:09:39 +02:00
LibretroAdmin 645be4f7ad audio: split sdl_audio.c into sdl1_audio.c and sdl2_audio.c
One file per SDL generation, each with its own vtable: audio_sdl1
("sdl") in sdl1_audio.c and audio_sdl2 ("sdl2") in sdl2_audio.c. The
SDL2 file keeps the microphone driver, which is SDL2-only. The SDL1
file drops the SDL2 device-API stubs it no longer needs and checks
SDL_OpenAudio()'s return properly - the shared file compared an
SDL_AudioDeviceID (unsigned) against < 0, so a failed open was taken
for a good one.

Makefile.common builds each object from its own SDL branch instead of
from HAVE_SDL_COMMON, griffin picks the file matching the SDL version,
and the samples/audio suites follow the renamed symbols.
2026-09-20 17:01:29 +02:00
LibretroAdmin 084dbf3e35 audio-suite-check: pass the suite's TSan suppression file
A samples/audio suite that ships tsan.supp does so because its reports
come from an uninstrumented dependency; latency_contract's covers
OpenAL Soft, whose own threads and atomics TSan cannot see into. The
workflow passes the file, so the hand check reports a race the CI job
does not.
2026-09-20 17:01:14 +02:00
LibretroAdmin 635cc12f9b Remove the dead thread handle, and stop SceUID being declared three times
The SceUID member in the three console audio drivers was never
assigned or read. On Vita it was the only thing those drivers wanted
from psp2/kernel/processmgr.h, threadmgr.h and sysmem.h, and
psp2/audioout.h includes psp2/types.h itself, so those three go with
it. PSP1 keeps pspkernel.h: pspaudio.h has no includes of its own and
relies on it for the types in its prototypes.

The Vita compile-only stubs each declared their own SceUID, which
collided under -pedantic in any translation unit taking more than one
of them - features_cpu.c takes processmgr.h and, through
retro_timers.h, threadmgr.h. The typedef moves to a psp2/types.h stub
where the real headers keep it, and the two that need it include it.
The sysmem.h stub held nothing else and no lane includes it now.
2026-09-20 16:39:59 +02:00
LibretroAdmin 488bd8d402 (PSP/Vita/PS4) Size the ring floor for a 30 Hz delivery
The ring has to hold a delivery beside the period the worker is
playing, and a delivery is a video frame of audio: 800 frames for
60 Hz content at 48 kHz, but 1600 for 30 Hz. The four-period floor
fitted the first and not the second, so at settings below the
default a 30 Hz core starved - 14% of periods handed silence through
the threaded pipeline, 93% through the inline one, where the whole
delivery goes in a single write.

Five periods is the floor, which is the ring the default 64 ms
setting already asks for. So no setting at or above the default
moves, 60 Hz content is unchanged at either floor, and 30 Hz content
at the low end goes to 0.3% and 4%. Settings below 64 ms now report
64 ms rather than 53 ms, which is the whole of the visible change.

Content slower than 30 Hz is not covered: 20 Hz wants a sixth
period, which would push the default itself to 75 ms. Left alone
rather than moved for content that rare.

samples/audio/psp_ring drives the starvation check at both delivery
sizes; the floor at four fails the 30 Hz one on all three and leaves
60 Hz green.
2026-09-20 16:32:04 +02:00
LibretroAdmin af297573df Share the NULL return in the SDL audio device list
sdl_audio_list_new returns NULL both from the guard on the list it
allocates and from the SDL1 arm. Wrapping the body in that guard lets
both paths fall through to a single return after #endif.
2026-09-20 16:31:04 +02:00
libretroadmin ecc7ad0c49 (wiiu_audio.c) Update 2026-09-20 16:23:17 +02:00
LibretroAdmin b414ab8ee0 (PSP/Vita/PS4) Play a period as soon as there is one
The worker handed the device silence unless two periods were held,
so at the low end of the latency range - where the ring is at its
four-period floor and the frontend delivers 800 frames at a time -
it spent a quarter of every period playing silence with audio
sitting in the ring. Measured against a device taking a period every
10.67 ms and a writer delivering a video frame of audio at 60 Hz:
27% of periods silent at the floor, against 0.3% when a single held
period is enough. At 64 ms and above the two are the same, so
nothing above the floor changes.

PSP1 needed the window it hands over to change with it. It was
giving the device the window after read_pos rather than the one
read_pos names, which is whole only when two periods are held; it
now takes the window read_pos names and publishes the index after
the call returns, as the other two already did. That also retires
the period it used to discard unplayed at startup.

samples/audio/psp_ring gains a starvation check at the lowest
setting, with the writer paced to the device rather than run flat
out. Putting the reserve back fails it on all three; leaving the
reserve out but giving PSP1 its old window back fails PSP1 alone on
a discontinuous window, which is the coupling between the two.

A failed check in the suite now releases the driver, so one failure
cannot leave a worker running against the next.
2026-09-20 16:20:24 +02:00
LibretroAdmin cbdcfbbdb5 psp1: keep every blit inside the GE's transfer limits
The GE's block transfer takes each side at 1023 at most and both
strides as a multiple of 8, no wider than 1024. The frame and menu
paths pass the core's width and pitch straight through, so a core
outside those limits gets a malformed transfer: vecx hands over
330-pixel rows and o2em 340-wide ones.

Round the destination stride, which is ours, up to a multiple of 8 and
carry it into sceGuTexImage; crop either side at 1023. A source stride
the transfer cannot express is copied a row at a time, where the
stride is not read, through a call list grown to the geometry - the
CPU writes 8 command words a row instead of repacking the frame
itself, which at 340x240 is 1920 uncached stores against 40800.

The harness holds all four limits on every blit and checks the frame
arrives in the texture for both stride paths.
2026-09-20 16:09:25 +02:00
LibretroAdmin 5198a6cb5a (PSP/Vita/PS4) Size the ring from the latency setting
The ring was a fixed 8192 frames on all three, which at 48 kHz is
170 ms of buffering whatever the user asked for, and half of that is
what rate control steers to. Size it from the latency setting at the
rate the device opened at, less the period the device itself holds,
so buffer_size() and the latency it implies are what was asked for.

The size stops being a power of two, so the wrap stops being a mask:
indices stay inside the ring and RING_HELD/RING_FREE compare instead.
The ring is a whole number of periods, which is what keeps every
window the worker hands over inside it, and the floor is four - the
worker wants two periods held before it hands one over, and the
writer needs room past that. At 48 kHz that floor is 53 ms reported,
so settings under it get the floor; above it the figure is exact.

The rate the device took is now kept, since the sizing needs it.

samples/audio/psp_ring gains a check that buffer_size() tracks the
setting across 8..256 ms rather than returning a constant: pinning
the ring back to 8192 fails it on all three. The ring's own checks
hold across the rewritten wrap - giving the reserved frame back still
fails all three with a discontinuous window.
2026-09-20 16:08:38 +02:00
LibretroAdmin 58aa63ebb7 Share the NULL return across the mixer loaders
Each audio_mixer_load_* function returns NULL both from the guard on its
allocation and from the #else arm for a codec that is not built. Wrapping
the body in that guard lets both paths fall through to a single return
after #endif.
2026-09-20 16:08:29 +02:00
LibretroAdmin b23aa20f28 (PSP/Vita/PS4) A harness for the console audio ring
The three drivers had no suite, so the two races fixed in ba5339c4
and d36404a1 had nothing holding them down.

samples/audio/psp_ring builds all three drivers for their own
platforms against the compile-only SDK stubs and supplies the bodies:
a device that reads the window it is handed and takes a period over
it. The writer's frames carry a running count, so a window whose
frames do not continue the last one means the writer reached data the
device had not finished with - a lapped ring, or a period published
free before the device took it - and the check holds without a
sanitizer.

Reintroducing either fix's absence fails it, and each fails its own
platforms: the room check that cannot tell a full ring from an empty
one fails all three, publishing read_pos before the output call fails
Vita and PS4 while PSP1 stays green, which is right - it hands over
the window after read_pos and frees one only on the iteration after
it played. Clean under ASan+UBSan+LSan and TSan.

tools/platform_stubs/orbis carries the sceAudioOut declarations, the
set the psp and vita stubs already had.

Not wired into Linux-samples-audio.yml; run it by hand, or through
tools/audio-suite-check.sh, which now finds it for all three drivers.
2026-09-20 16:01:16 +02:00
LibretroAdmin d36404a114 (PSP/Vita/PS4) The room check could not tell a full ring from an empty one
write_pos and read_pos are both masked into the ring, so the writer
having filled every frame and the writer having written nothing both
read as a distance of zero, and the free-space computation answered
with the whole ring. A writer that caught up exactly lapped: it wrote
from read_pos forward, over the window the device was reading, while
the worker - reading the same distance - took the full ring for an
empty one and played silence.

Hold a frame back, so the two states differ.

Found with a threaded model of the mainloop against the write path
using the tree's own retro_atomic ops, under TSan: the race reproduces
with the frame returned and does not with it held back, and it is
independent of the read_pos publication order - each fix is needed and
neither covers the other.
2026-09-20 15:54:45 +02:00
LibretroAdmin ba5339c419 (Vita/PS4) Publish read_pos after the device has taken the window
The worker advanced read_pos and released it before handing the same
window to sceAudioOutOutput, so the period the device was reading was
already the writer's to fill. A write landing in it while the call
was in flight put the writer's samples into audio being played.

The output call returns once the device has taken the window, which
is where the period becomes free: move the release store there, and
the notify with it. PSP1 does not have this - it outputs the window
after read_pos, which is still-unread data, and frees a window only
on the iteration after it played.
2026-09-20 15:54:42 +02:00
LibretroAdmin 0127835abb psp1: let a build pick the frame slice grid
The 4x16 split makes each slice tall and narrow, which is the wrong
shape for an unswizzled texture's cache lines; what another split
costs can only be measured on the device. Take both counts from the
build when it sets them, and add a check-grids target that holds the
coordinate contracts for each candidate so only speed separates them.
2026-09-20 15:54:13 +02:00
LibretroAdmin 4853a53946 psp1: take blue from the high bits of the display formats again
Red sits in the low bits of every PSP display format, which is what
the CLUT passes psp_init() builds convert the core's frame into: the
pass indexed at red_shift writes the low channel, the pass indexed at
0 writes the high one. So read_viewport's 565 and 5551 arms take blue
from the top of the pixel, as its 4444 and 8888 arms already do.

Record the layout next to both, and in the harness, whose expected
bytes for the two 16-bit cases disagreed with its own 4444 and 8888
cases.
2026-09-20 15:51:20 +02:00
LibretroAdmin 32315f6a74 (PSP/Vita/PS4) Report consumed frames and underruns
These three reported neither, so the sink rate estimate had nothing
to trim the resampling ratio with and the statistics overlay showed
no starvation for any of them.

Neither SDK offers an underrun signal or a queued-sample query the
drivers can use - the PSP SRC's output call returns the sample count
it was reserved with, and sceAudioOut has no rest-sample equivalent
on PS4 - but both are available from the worker's own cadence. The
output call returns once the device has taken the period before it,
so a completed call is a period consumed; the branch that hands the
device silence is the starved one. One atomic add each, where it
happens, read by the frontend.

Vita is the one that could do better: sceAudioOutGetRestSample()
reports a main port's unplayed samples, so frames_consumed() there
could be what was released less what the device still holds rather
than the cadence. Left for someone who can watch it against the
overlay on hardware, since that number steers rate control.

buffer_size() left out the period the output call holds while the
device plays it, which is buffering the driver controls; the
reported size and the latency it implies now agree.
2026-09-20 15:47:47 +02:00
LibretroAdmin c036af814a psp1: give the vblank handler the signature the kernel calls it with
sceKernelRegisterSubIntrHandler() invokes the handler as
void (*)(int, void *), so declaring it otherwise leaves it called
through an incompatible type. Take the driver from the void* argument,
and move the psp_set_texture_enable() declaration ahead of the
statement before it; the compile lane now rejects both.
2026-09-20 15:46:55 +02:00
LibretroAdmin 9c258b507a psp1: add a contract harness for the PSP video driver
Links gfx/drivers/psp1_gfx.c against a stand-in for the machine it
depends on: main RAM and VRAM each mapped twice so the cached and
uncached aliases alias, a no-access page after VRAM, display lists
that stay the GE's until waited on, and a vblank sub-interrupt that
can fire inside free().

Holds the read_viewport byte order and buffer fill, the teardown
order, GE ownership of lists and framebuffers, both blit bounds,
cache-line ownership of what the GE writes, the coordinate grid
across all four rotations, vsync pacing, and init under allocation
failure.
2026-09-20 15:46:49 +02:00
LibretroAdmin 5d16bb5c26 psp1: wait for the GE before drawing the OSD message
The message goes into the framebuffer through the CPU. With a
hardware-rendering core the driver deliberately leaves the GE alone,
so it has to be waited on here, where the two would otherwise be
writing the same buffer.
2026-09-20 15:46:45 +02:00
LibretroAdmin cf4bc9dd1e psp1: fill the whole read_viewport buffer
The screenshot task sizes its buffer from the viewport and encodes all
of it, so a viewport reaching past the framebuffer has to come back
black there rather than leave the encoder reading uninitialised heap
with every row shifted. Clear the buffer, place each row at the offset
the viewport gives it, and read the framebuffer through the uncached
alias the GE wrote it through.
2026-09-20 15:46:42 +02:00
LibretroAdmin a5c0adc3c7 psp1: emit BGR from read_viewport in the 16-bit display modes
video_driver.h specifies read_viewport as 24bpp BGR, which the 4444
and 8888 arms answer with; the 565 and 5551 arms emit the channels the
other way round, and those are the modes the PSP runs in, so every
screenshot comes out with red and blue exchanged.
2026-09-20 15:46:38 +02:00
LibretroAdmin 68c202cbd4 psp1: rebuild texture coordinates only when the geometry changes
psp->frame_coords is written through the uncached alias, so rebuilding
it is 256 stores straight to memory every frame. Keep the source
geometry they were built for and skip the rebuild while it holds.
2026-09-20 15:46:33 +02:00
LibretroAdmin 24ac206e51 psp1: bound the core frame and menu blits to their buffers
VRAM holds the display buffers and the colour tables before the core
frame, and the menu texture buffer holds one screen of 4444; a core or
menu framebuffer larger than what is left runs sceGuCopyImage() off
the end of either. Record what VRAM is left at init and clamp the
copied height in both paths.
2026-09-20 15:46:29 +02:00
LibretroAdmin a0a7a33354 psp1: fail init when an allocation does
psp_init() dereferences the coordinate buffers straight after
allocating them, and hands back a driver whose display lists the
frontend will pass to sceGuStart(). Check every allocation and tear
the partial driver down through psp_free() instead.
2026-09-20 15:46:26 +02:00
LibretroAdmin 4cef57434c psp1: mark the vblank flag volatile
psp_on_vblank() clears it from interrupt context while psp_frame()
reads and sets it, so the compiler has to reload it rather than keep
it in a register. On PSP this is what retro_atomic's own backend
reduces to.
2026-09-20 15:46:22 +02:00
LibretroAdmin 3e67043bd6 psp1: wait for the GE before rebuilding the main display list
sceGuFinish() hands the list to the GE and returns, so the buffer is
still the GE's when the menu pushes a texture between frames.
Synchronise before refilling it, and ignore a call with no driver or
no frame.
2026-09-20 15:46:19 +02:00
LibretroAdmin b7ee4aac47 psp1: reserve whole cache lines for the buffers the GE writes
The GE context save area and the menu texture are written by the GE,
so a cache line either of them shares with data the CPU writes is lost
to the next writeback. Give the context storage its own 64-byte
aligned allocation instead of a slot in the driver struct, and raise
the menu texture's alignment to a cache line.
2026-09-20 15:46:15 +02:00
LibretroAdmin 7b874f570a psp1: release the vblank handler before freeing the driver
psp_on_vblank() writes through the driver pointer it was registered
with, so the sub-interrupt has to be disabled and released before
psp_free() releases anything it reaches. Free the driver struct itself
by that pointer, and drop the main_dList guard so a driver whose first
allocation failed is still torn down.
2026-09-20 15:46:11 +02:00
LibretroAdmin 4a2c2bde57 psp1: compile the PSP video driver in the compile matrix
gfx/drivers/psp1_gfx.c is reachable only through the griffin unity
build, so nothing in CI compiles it. Give it a platform_video lane,
and extend the PSP stub header set with the kernel, display and GU
shapes it names.
2026-09-20 15:46:08 +02:00
LibretroAdmin 4ac1c11a77 Share the fallback return instead of duplicating it per arm
Three functions carry a #else arm whose only job is to return the same
constant the enabled arms already fall back to. Inverting the guard that
precedes each arm's final return lets the arm fall through, so the
constant is returned in one place after #endif.
2026-09-20 15:45:06 +02:00
LibretroAdmin 6d416e7a41 Remove empty preprocessor conditionals
Sixteen #if/#endif pairs across twelve files enclose nothing, so they
select no code in any configuration. Removing them leaves preprocessor
output identical.
2026-09-20 15:29:54 +02:00
LibretroAdmin 814b4cf6cb (PSP/Vita/PS4) One arena for the ring and its silence
The ring and the period of silence the worker outputs when the ring
runs short were two allocations of the same lifetime, neither ever
resized and the silence never written after init. Put them in one
uint32_t arena, the silence past the ring's end, so init has one
allocation to check and free one pointer to release.

The window handed to the output syscall stays inside the ring:
read_pos starts at zero and only ever advances by AUDIO_OUT_COUNT,
which divides AUDIO_BUFFER_SIZE, so it is always a multiple of the
period and the last window ends exactly at the ring's end. The
producer's wrap splits at that same boundary.
2026-09-20 15:29:37 +02:00
LibretroAdmin c68798c71d (PSP/Vita/PS4) Negotiate the output rate, harden init, implement PS4 stop
Each of these backends takes a fixed set of sample rates, and a
setting outside it was passed straight to the hardware: the port
open failed, init returned NULL and the session ran with no audio
at all. Report the rate actually opened through new_rate instead, as
gx_audio does, so the frontend resamples to it.

 - PSP1: the SRC's rate list is fixed, so fall back to 48 kHz when
   the requested rate is refused.
 - Vita: the main port opens at 48 kHz only.
 - PS4: sceAudioOut opens at 48 kHz only.

The ring and silence buffers were allocated without checking the
result, and neither allocation failure nor a failed eventcount init
released the port that had already been opened. Allocate with calloc,
check both, and release the port on every failure path.

PS4: sceAudioOutInit() ran after the port it initialises was already
open; it now comes first. stop() returned false unconditionally, so
CMD_EVENT_AUDIO_STOP always failed on Orbis - pause, sleep/wake and
the shutdown path with it. It stops the worker and reports the
resulting state, as the vtable's contract asks and the other two do.
2026-09-20 15:15:36 +02:00
LibretroAdmin fa553ea47c Split psp_audio.c into per-platform audio drivers
The PSP1, Vita and PS4 backends share no SDK: each opens its port
with a different call, outputs through a different syscall and
releases differently. Give each one its own file and vtable.

audio/drivers/psp_audio.c  - PSP1,  audio_psp,  "psp"
audio/drivers/psp2_audio.c - Vita,  audio_psp2, "vita"
audio/drivers/ps4_audio.c  - Orbis, audio_ps4,  "orbis"

Driver idents are unchanged, so existing configs keep working.
Makefile.common, Makefile.orbis and griffin select the file for the
platform being built; the matrix's Vita lane now compiles
psp2_audio.c.

ps4_audio.c drops the unused verbosity.h include, which on ORBIS
pulls in debugnet.h, and declares malloc through stdlib.h.
2026-09-20 15:07:21 +02:00
LibretroAdmin 02fbc9f3c7 (Vulkan) A check holds the order teardown retires driver objects in
tools/vulkan_teardown_order_check.py fails when vulkan_free() or vulkan_check_swapchain() retires the command pools, the descriptor pools and the pipelines in any order but innermost first, when a descriptor set is freed by hand rather than by the pool destroy that owns it, and when the pool asks for per-set free that nothing needs. The order is legal Vulkan and the validation layer passes it, so the lavapipe harness beside it cannot see this class; a driver that walks its own back references as a pool is destroyed faults at exit instead. Runs as its own job in the Vulkan workflow, self-test first, like the idle-wait check.
2026-09-20 14:55:55 +02:00
LibretroAdmin 0682d0c6e5 (Vulkan) Descriptor pool blocks are created without per-set free
The sets in a block live and die with the pool, and VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT is required only by vkFreeDescriptorSets, which nothing calls. Drivers that provide a per-set allocator for the flag no longer hand one out. The slang filter chain's pools are created this way already.
2026-09-20 14:55:52 +02:00
LibretroAdmin 17fe7a8157 (Vulkan) Retire command pools and descriptor pools before the pipelines
A command buffer references the descriptor sets and the pipelines it was recorded with, and a descriptor set references the layout it was allocated from, so teardown runs innermost first: command pools, then descriptor pools, then the pipelines that own the set layout. Destroying a descriptor pool releases every set allocated from it, so that one call frees them, and the per-frame command buffer and pool handles are cleared as they go, like the descriptor managers and buffer chains beside them.

A driver that keeps its own references along that chain walks them as each pool is destroyed - MoltenVK does, and the fault lands inside vkDestroyDescriptorPool on the video thread as RetroArch exits.
2026-09-20 14:44:48 +02:00
libretroadmin c75c752edb Cleanup 2026-09-20 14:34:01 +02:00
LibretroAdmin b98eefe946 (Metal) A streaming texture stages through one slot per frame in flight
With a single staging buffer, the busy flag set by frame N's blit is
only cleared when the GPU finishes that blit's command buffer. With up
to MAX_INFLIGHT frames queued ahead of it, that has usually not
happened by the time frame N+1's update arrives, so a texture updated
every frame dropped most of its frames and streamed at a fraction of
its rate.

The staging buffer now holds MAX_INFLIGHT frame-sized slots, each with
its own busy flag, used round-robin - the same shape as Vulkan's
VK_STREAM_SLOTS. A frame is dropped only when the slot it would use
is still being read, which with one slot per frame in flight is rare.
The flags are plain ints behind __atomic stores and loads: each is set
only by the updating thread and cleared only by the completed handler,
so no read-modify-write is shared between them.
2026-09-20 14:16:24 +02:00
LibretroAdmin 5dc2d285fa (Metal) A streaming update runs on the thread that owns the blit buffer
metal_update_texture now encodes a blit into Context.blitCommandBuffer,
but it still ran inline on the caller's thread whatever threaded said.
Under threaded video that is the main thread, while the video thread
encodes into and commits the same command buffer at frame end.
MTLCommandBuffer is single-thread-only, and a completed handler added
after the video thread has committed the buffer is an exception, not
just a race.

metal_load_texture already routes mipmapped loads to the video thread
for exactly this reason, and video_driver.h says update_texture is
marshalled onto the video thread; Vulkan, D3D11 and D3D12 all do it.
Metal now does the same through video_thread_texture_handle, with the
result coming back through cmd.handle as in metal_texture_load_wrap.
2026-09-20 14:16:24 +02:00
libretroadmin 2421ce7bb9 (Metal) The staging buffer is not written while the GPU still reads it
The blit reads the staging buffer on the GPU and the next frame's
memcpy writes it from the CPU: hazard tracking orders one GPU access
against another and says nothing about that, so the race moved from
the texture to the buffer rather than going away. The texture carries
a flag now, set when a blit that reads the buffer is committed and
cleared from that command buffer's completion handler, and a frame
arriving while it is set is dropped - the same policy Vulkan, D3D11
and D3D12 keep, with the buffer rather than the texture as the thing
being protected. The flag is atomic because Metal finishes on
whatever thread it likes, and the handler holds the texture so the
flag is still there to clear.
2026-09-20 14:16:24 +02:00
libretroadmin daa9703f7d (Metal) A streaming update is a blit the GPU orders, not a write over its shoulder
metal_update_texture wrote the new contents with replaceRegion:, which
lands from the CPU the moment it is called - while the command buffers
already committed may still be sampling that texture. The comment
above it justified this by saying a command buffer retains what it
samples, which is true of the object's lifetime and says nothing about
its contents. Every other backend refuses exactly this: Vulkan checks
its fences, D3D11 maps with DO_NOT_WAIT, D3D12 compares a fence tag,
and each drops the frame rather than race.

The pixels go through a staging buffer per texture and a blit on the
shared blit command buffer instead. It executes in commit order with
the draws around it and Metal's hazard tracking orders the write
against the reads, so a streaming frame is neither raced nor dropped -
better than the drop the others settle for. The buffer is made on the
first update and kept, since a texture that streams updates every
frame, and it is released with the texture.
2026-09-20 14:16:24 +02:00
LibretroAdmin 26a1a303d0 ci: stop overlay-on-screen runs on master cancelling each other
The concurrency group was keyed on the ref with cancel-in-progress
set, so every push to master that touched one of the watched paths
cancelled the run still going for the previous commit ("Canceling
since a higher priority waiting request ... refs/heads/master
exists").  With several commits pushed close together, most of them
never got the test run to completion.

Key the group on the commit SHA for pushes and only cancel in
progress runs for pull requests, where a new push really does
supersede the old one.
2026-09-20 13:49:40 +02:00
LibretroAdmin 46973bd825 videocore: read the EDID, and use bcm_host's gencmd client
dispserv_videocore gets get_edid, reading the display's EDID over
HDMI DDC with vc_tv_hdmi_ddc_read() the way tvservice -d does: the
base block, then the extension blocks it announces, as far as the
buffer goes. The edid CRT preset (mode 5) takes its ranges from it
instead of warning and falling back to generic_15, and the EDID
entry under System Information shows it. Composite and DPI outputs
have no DDC; the read fails there and the fallback stays. Behind an
HDMI-to-VGA adapter the block is whatever the adapter reports, which
may be its own rather than the monitor's.

bcm_host_init() already connects the gencmd and tvservice clients.
The server now calls it at init (it returns at once when the video
driver made the call first) and sends hdmi_timings through that
client. The per-call connect and vc_gencmd_stop() it replaces, carried
over from crt_rpi_switch, stopped the gencmd client bcm_host shares.
2026-09-20 13:45:52 +02:00
LibretroAdmin b6f5b2d27d crt: VideoCore becomes a display server
The legacy Raspberry Pi firmware stack had its own CRT switching path
inside video_crt_switch.c: a hardcoded timing heuristic
(crt_rpi_switch) that bypassed the modeline engine, with every engine
call site fenced off by #if !defined(HAVE_VIDEOCORE). Keeping those
fences in step with the call sites is how 94d23651 broke the link on
Lakka's Pi Zero builds (fixed in 4e057a5).

VideoCore now has a display server like KMS and X11 do.
dispserv_videocore implements the modeline ops: the engine generates
the mode, and set converts it to hdmi_timings (porches and pulse
widths, inverted-sync flags, frame rate for interlaced modes), sends
it through gencmd, selects it with tvservice -e "DMT 87", resizes the
framebuffer with fbset and reinits the video driver, the same sequence
crt_rpi_switch ran. The vc_egl context and the dispmanx video driver
report RARCH_DISPLAY_VIDEOCORE so the server is picked up.

With that, video_crt_switch.c has no HAVE_VIDEOCORE left:
crt_rpi_switch and the crt_switch_write_edid stub are gone, VideoCore
builds get switchres.ini overrides and the CRT menu entry, and the
guard on crt_switch_display_server_lost() in video_driver.c goes back
to HAVE_MODELINE alone.

Behaviour differences on VideoCore:
- Timings come from the engine and the monitor preset instead of the
  old heuristic.
- Without a super width set by the user, the policy locks a 1920
  super resolution, as the old path always widened the mode near 1920
  to keep the HDMI pixel clock up. It scales fractionally where the
  old path picked an integer multiple of the core width.
- Doublescan is disabled; hdmi_timings has no way to express it.
- The last mode still stays on the wire at exit; the firmware has no
  desktop mode to restore.
2026-09-20 13:43:00 +02:00
Shigeaki Asai 4e057a5194 video_driver: guard crt_switch_display_server_lost with HAVE_VIDEOCORE
crt_switch_display_server_lost() (video_crt_switch.c) is defined
inside #if !defined(HAVE_VIDEOCORE) - VideoCore keeps its own
tvservice path and doesn't use the modeline ops table. The call
added in 94d23651 (\"crt: rebind the modeline ops when the display
server is rebuilt under a live engine\") only checked HAVE_MODELINE,
so any build with both HAVE_MODELINE and HAVE_VIDEOCORE (e.g. Lakka
RPiZero/RPiZero2 with the bcm2835-driver GLES stack) fails to link:

  video_driver.c:(.text+0x20e8): undefined reference to
  \`crt_switch_display_server_lost'

Add the same !defined(HAVE_VIDEOCORE) guard at the call site.
2026-09-20 13:33:46 +02:00
LibretroAdmin 055383c3de d3d11 sample: the norebind control is a control again
The last patch folded version 3 into version 2 and renamed the test's
modes with it, which broke the run that has to fail:

  == a core that ignores what lock_context tells it (must fail)
  d3d11_hw_interface_v2: ok
  FAIL: it passed; the frontend in the test no longer disturbs the
  context

The frame bound everything under "if (rebind || v3)", because in version
3 the render target changes with the sync index and must be set every
frame whatever lock_context said. With version 3 now being version 2,
that condition is always true: the core rebinds even when it is
pretending not to, so the control could not fail.

The render target is now bound every frame on its own, outside the
rebind block where it belongs - it is the sync index's, not the
context's - and everything else is bound only when lock_context says the
frontend has had the context. A core that ignores lock_context draws
into the right texture and draws it wrong, which is the point.

run.sh went with it: the version 3 run is the version 2 run now, so it
is gone, and v3-nowait is v2-nowait. Three runs, one that must pass and
two that must fail.

Ran here under Wine on llvmpipe, the same way CI does: the contract run
passes, both controls fail.
2026-09-20 13:26:54 +02:00
LibretroAdmin 35a3daa09f android: only alias DPAD_CENTER to ENTER on the keyboard row
cf3c678de5 (#18405) rewrote every AKEYCODE_DPAD_CENTER press to
AKEYCODE_ENTER before storing it in android_key_state[], regardless
of which row it was written to. That fixed Enter on external
keyboards (rarch_key_map_android can only map RETROK_RETURN to one
keycode), but pad rows are read by raw keycode against joypad binds,
so any device whose OK/Center button is autoconfigured as "23" - the
Amazon Fire TV Remote, and other remotes/pads - could never trigger
that bind again: the press always landed in bit 66.

Restrict the alias to ANDROID_KEYBOARD_PORT, the only row read
through rarch_keysym_lut. Scoping on the row rather than on
AINPUT_SOURCE_DPAD matters: the hotplug code classifies
KEYBOARD|DPAD (0x301) devices as keyboards too, and ordinary
keyboards with arrow keys report exactly that, so a source check
would reintroduce #18395 for them.

Fixes #19593
2026-09-20 12:52:35 +02:00
LibretroAdmin 81744eb571 d3d11: version 3 becomes version 2, and the old version 2 is gone
The D3D11 hardware render interface had two negotiated versions above 1:
version 2, where the frontend is finished with the frame's texture by
the time video_refresh returns, and version 3, which adds a sync index
so the texture is handed over without a copy. Version 2 was never used
by anything - no core shipped against it - and keeping it meant the
frontend carried a copy path for a case that does not arise.

So version 3 is version 2 now, and what version 2 was is deleted. There
is one negotiated version above 1 and it is the one with the sync index,
which is also how libretro_d3d12.h version 2 and the Vulkan interface
work. RETRO_HW_RENDER_INTERFACE_D3D11_VERSION_3 is gone; a core that
named 3 as its maximum gets 2, and behaves exactly as before.

What went with it:

  - The copy branch in d3d11_gfx_hw_ring_capture, taken when the ring
    passed D3D11_HW_RING_CAPTURE_TEXTURE, and the macro itself.
  - The threaded wrapper's d3d11_v3 flag and the two paths behind it.
    hw_ring_install is now required for version 2 at all, since handing
    the texture over without a copy is what version 2 is; a frontend
    without it gives the core version 1.
  - The sample test's separate version 2 mode; "v3" and "v3-nowait"
    become "v2" and "v2-nowait".

Not built here - this is Windows-only code and needs a Windows build
before it is trusted.
2026-09-20 12:35:14 +02:00
LibretroAdmin 7410f9e8af d3d12: stock pipelines are built for the swapchain's actual format
A 10-bit swapchain with HDR off renders nothing. The game's audio and
input run, the log is clean, and the screen is black.

d3d12_gfx_init_pipelines hardcoded RTVFormats[0] to R8G8B8A8_UNORM for
every pipeline that draws to the swapchain. That is right whenever the
swapchain is 8-bit, and it is not always 8-bit: with HDR disabled and
video_swapchain_bit_depth set to 10, chain.bit_depth becomes
DXGI_SWAPCHAIN_BIT_DEPTH_10 and the back buffer is R10G10B10A2_UNORM. A
draw whose pipeline disagrees with its render target is invalid, so
every frame is thrown away.

It only bites with HDR off, which is why it looks backwards: with HDR on
the frame is drawn with the HDR pipes, and those have always been built
from the real swapchain format and recreated when it changes. The SDR
ones never were. So they are now built the same way, from
chain.formats[chain.bit_depth], which is set well before the pipelines
are created.

The debug layer says it plainly once it is asked:

  ID3D12CommandList::DrawInstanced: The render target format in slot 0
  does not match that specified by the current pipeline state.
  (pipeline state = R8G8B8A8_UNORM, render target format =
  R10G10B10A2_UNORM)

repeated for every draw of every frame.

Not built here; this needs a Windows build before it is trusted.
2026-09-20 05:54:55 +02:00
LibretroAdmin 0e661e7be4 input/overlay: compose two-frame APNGs once, retry busy frame swaps
Two fixes to the press-driven frame swap:

- A swap that could not happen - the surface still in flight on the
  video thread, or the submit failing - was recorded as done anyway.
  Under threaded video a quick tap submits the pressed frame, the
  release lands while it is still in flight, and the button then stays
  pressed-looking until the next tap. The swap now reports whether it
  happened and the current frame only changes when it did, so the
  next poll retries.

- Every press and release rewound the stream and inflated one or two
  full frames on the main thread (the second frame is composed over
  the first, so it cannot be decoded alone). Both frames are now
  composed once at load into a back-to-back pair and a swap is a copy.
  An APNG whose pair cannot be composed, or whose canvas does not
  match its decoded image, stays an ordinary looping animation.

Also reset the current frame when the surface is re-created, since the
new texture starts on the unpressed frame.
2026-09-20 02:40:44 +02:00
LibretroAdmin 4905b828b3 input/overlay: index two-frame APNG press state by pack image
desc->image_index is the desc's position in its page's image list:
every desc with an image gets its own entry there, even when several
share one file. The anim_2frame arrays are sized num_images, one entry
per unique image of the pack, deduplicated by path. Indexing them with
image_index read (and, when the stray byte was nonzero, wrote) past
their end on any pack whose page has more image entries than the pack
has unique files - e.g. a dozen buttons sharing one png - and on
smaller packs toggled the wrong image, since page index N is not
unique image N and the page background shifts everything by one.

Record the unique index at load time, where the loader already has it
from its dedup lookup, as desc->pack_image_index, and use that. Also
skip image-less descs, whose calloc'd index of 0 would otherwise press
whichever file is unique image 0, and keep the block under HAVE_RPNG
with the rest of the animation code.
2026-09-20 02:40:44 +02:00
hunterk 2de98cc73b add on-press functionality for 2-frame apngs 2026-09-20 02:40:44 +02:00
github-actions 687836cfa3 Fetch translations from Crowdin 2026-09-20 00:27:49 +00:00
libretroadmin 2bd0cf51ac Threaded video: the poke interface is published under the lock
video_thread_get_poke_interface() had the driver write thr->poke
directly, on the main thread, while the video thread was already
running and reading that field - the asynchronous upload list takes it
under thr->lock every time it drains, so the read was guarded and the
write was not. A reinit while uploads are in flight is where the two
meet, which is the shape the font-marshal lane drives and where TSan
caught it.

The driver fills a local now and the lock publishes it, so both sides
of the field are under the same lock. Nothing else writes it: the
driver_data assignments beside it happen in the command handler, on
the thread that reads them.
2026-09-20 02:05:52 +02:00
Quan Wei b601ad56e2 ci: package Windows ARM64 builds for GitHub releases (#19586) 2026-09-19 23:53:36 +02:00
LibretroAdmin 8b4b3bfcc4 Threaded video: the hardware ring reports the interface version once
[Video] Threaded video: D3D11 hardware render interface version 3, ...
  [Video] Threaded video: D3D11 hardware render interface version 3, ...

A core may ask for the hardware render interface more than once - when
it first needs the context, and again when it builds its device - and
the ring logged its answer every time. The answer is the same each
time, so it is logged once per ring, and again only if it ever differs.
A new ring, after a reinit, says it again, which is when it is worth
reading. Same for the D3D12 line.

Passes tools/mingw_syntax_check.sh.
2026-09-19 22:16:21 +02:00
LibretroAdmin 0b83e4f881 (D3D12) Interface version 2: the frame is drawn from the core's texture, not from a copy of it
As d804bcb did for D3D11. The driver copies every hardware frame into
frame.texture[0] and draws from that, so that shader passes, history and
feedback read one texture the driver owns; with no shader preset active
nothing needs it, and it is a full-frame copy every frame.

For a core on interface version 2 it goes. Such a core keeps a texture
per sync index and leaves one alone until the frontend is done with it,
so the texture stays the frame it was handed over as for as long as the
driver draws from it, repeats and the menu's redraws included.

What D3D12 needs that D3D11 did not:

  - a descriptor for the core's resource in the driver's own heap. A
    core rotates a few textures, so one is made per resource and found
    again; four are kept, with their resources, until another wants the
    place. That only happens when a core's textures change - a change of
    resolution - and work recorded with the old descriptor may still be
    on the queue, so the queue is drained before it is written over;
  - the resource's state. The core leaves its texture in required_state,
    COPY_SOURCE, and expects to find it there. It is made
    PIXEL_SHADER_RESOURCE for the one draw that reads it and put back,
    in the same command list, on every frame that draws from it.

That draw is now what reads the core's texture, so it is what arms the
fence wait_sync_index waits for, where the copy did. SDR and HDR draw
the frame through the same one binding, so both get this. A shader
preset that is active, a frame from interface version 1 or a software
core, or a descriptor that cannot be had, all take the copy as before;
only a core that negotiated version 2 reaches any of this.

With the core handing its merge target over without a copy (lrps2
000ca3eb5), a D3D12 frame now goes from the core to the screen with
none at all.

Passes tools/mingw_syntax_check.sh (win32 gnu99 and the C89 lane). Not
run.
2026-09-19 22:05:49 +02:00
LibretroAdmin d804bcbfb3 (D3D11) Interface version 3: the frame is drawn from the core's texture, not from a copy of it
The driver copies every hardware frame into frame.texture[0] and draws
from that, so that shader passes, history and feedback all read one
texture the driver owns. With no shader preset active nothing needs it:
it is a full-frame copy, every frame, to draw a picture that is already
in a texture.

For a core on interface version 3 it goes. Version 3 is what makes it
safe: such a core keeps a texture per sync index and leaves one alone
until the frontend is done with it, so the texture stays the frame it
was handed over as for as long as the driver draws from it - repeats
and the menu's redraws included. The driver makes a shader resource view
of the core's texture (a core rotates a few, so a view per texture is
made once and found again; four are kept, with their textures, until
another wants the place) and binds it where it bound frame.texture[0]'s.
SDR and HDR draw the frame through that same one binding, so both get
it. With version 3 already handing the texture over without a copy, a
frame now goes from the core to the screen with none at all.

Everything else is as it was: a shader preset that is active, a frame
from any other interface version or from a software core, or a view that
cannot be made, all take the copy. Only a core that negotiated version 3
reaches any of this.

Passes tools/mingw_syntax_check.sh (win32 gnu99 and the C89 lane). Not
run.
2026-09-19 22:00:12 +02:00
libretroadmin 6957451cf2 CI: the Metal behaviour the surface and overlay paths assume
The driver's Metal paths have only ever been compiled: no job runs
them and no hardware here can. Two assumptions carry them, and this
asserts both on the macOS runner's own device.

A streaming surface replaces a texture's contents while the GPU may
still be sampling it. Vulkan checks a fence, D3D11 maps with
DO_NOT_WAIT, D3D12 compares a fence tag, and each drops the frame
instead of racing; Metal's replaceRegion: has nothing of the kind, so
the path rests on Apple's rule that a managed or shared texture may be
written from the CPU. The lane writes sixty-four frames with work in
flight against each and reads the texture back.

An overlay page holds textures the pack owns. The page keeps them in
an array, a switch replaces that array, and dropping a page must not
take the pack's textures with it - the case that broke gl2 on Android.
The lane builds two pages over one set, switches, drops, and uses the
textures afterwards.

The sample does not link metal.m, which would drag in the frontend; it
reproduces the two patterns. A runner without a Metal device reports
77 and the step says so rather than passing quietly.
2026-09-19 21:50:07 +02:00
LibretroAdmin 3b45449ca3 libretro_d3d11.h version 3: a sync index, so the frame is handed over without a copy
Version 2 says the frontend is finished with the frame's texture when
video_refresh returns. A frontend that presents from another thread can
only honour that by copying the texture there, which RetroArch's
threaded ring does: a full-frame copy every frame, to move a picture the
core already has in a texture into one the ring owns.

Version 3 is version 2 with the sync index the Vulkan interface and
libretro_d3d12.h version 2 have: get_sync_index, get_sync_index_mask and
wait_sync_index. The core keeps one present texture per index; the
frontend reads the texture itself, when its own thread gets to it; and
wait_sync_index returns once the frontend is done with what the index
carried last time. No fence, unlike D3D12: the device has one context,
and what the frontend has issued on it is ahead of whatever the core
issues next, so "the frontend's frame call has returned" is all that
done has to mean. wait_sync_index is not called with the lock held, for
the reason video_refresh is not. It is a new version number and not new
members on version 2 because version 2 has shipped: a core built against
a longer struct must be able to tell a frontend that has it from one
that does not.

The driver, with no wrapper in front, reads the texture inside
video_refresh: one index, nothing to wait for.

The threaded ring holds a reference to the core's texture in the slot
where version 2 made a copy, and the video thread hands it to the driver
through hw_ring_install, which D3D11 now implements; the driver takes
its frame's reference under the lock, as it does for version 2's copies.
The wait that covers a frame queued but not yet claimed, or claimed and
still being recorded - written for D3D12 version 2, where a slot is the
core's own texture for the same reason - is lifted out of the D3D12
block and used by both.

samples/gfx/d3d11_hw_interface_v2 gains two runs: version 3 with a
frontend slower than the core, which laps it and has to wait, and a core
that skips wait_sync_index, which is caught ("index 0 should hold frame
0, holds frame 18"). All four runs behave; run.sh requires the two
controls to fail. d3d11.c and video_thread_hw.c pass
tools/mingw_syntax_check.sh (win32 gnu99 and the C89 lane). The driver
and ring code itself is not run: it needs the core's half.
2026-09-19 21:48:24 +02:00
LibretroAdmin d0fb3a8ef0 (D3D11) Interface version 2: video_refresh without the lock, and the frontend's turn comes first
Version 2 as first written cost a D3D11 core frame rate under threaded
video instead of giving it anything: about 50 fps from a core that runs
at 60, the wrapper's drains piling up, 20 ms and more to vblank. Two
things in the contract and the lock, both fixed here; the core's half of
the first follows in its own tree.

video_refresh was to be called with the lock held. video_refresh is
where the threaded wrapper paces the core: it waits, up to a refresh,
for the video thread to take the frame before. The video thread cannot
finish the frame it is on without the lock. So every paced wait ran to
its timeout with the core holding the one thing that would have ended
it. The contract now says what it should have: set_texture with the
lock held, release it, then video_refresh, and never hold it across
video_refresh. The frontend takes the lock itself for the copy it makes
there - it always did, the lock being recursive - and the core cannot
draw into the texture meanwhile because it is inside video_refresh. The
paragraph about carrying on after video_refresh within the same lock
goes, since there is no such thing any more.

A Windows critical section is not fair. A thread that releases it and
asks again at once usually gets it back, over one that has been waiting;
a core that locks around each piece of GS work, thousands of times a
frame, keeps the frontend's thread out for the whole frame that way. The
frontend now says when it is waiting (an interlocked count around its
EnterCriticalSection), and the core's lock_context lets it go first:
while the count is up it yields the processor instead of entering. A
nested lock_context does not do that - the core would be waiting for a
frontend it is itself blocking - which is what core_depth is for. With
no wrapper in front the frontend is the core's own thread and never
waits.

samples/gfx/d3d11_hw_interface_v2 follows the contract as changed and
still passes, control included. d3d11.c passes
tools/mingw_syntax_check.sh (win32 gnu99 and the C89 lane). Not run on
the driver itself.
2026-09-19 21:17:40 +02:00
LibretroAdmin c69cbc61f8 (D3D11) Interface version 2: the repeat of the last frame gives the context up around its waits too
A D3D11 core on interface version 2 under threaded video, 120 Hz display,
60 fps core: stuck near 48 fps, 8.33 ms reported for the driver's render,
20 ms and more to vblank.

cb758f6 stopped the frame holding the lock the core and the driver share
the context with through the frame's waits. It left the other entry
point that takes that lock as it was: d3d11_present_last, which puts the
retained frame on screen again when the core has nothing new by the
deadline. With a core slower than the display that is not a rare path -
at 120 Hz under a 60 fps core it runs every other refresh - and it waits
for the display exactly as the frame does: the waitable swapchain's
latency wait, then a Present with the swap interval. It held the lock
through both, so for one refresh in two the core could not touch the
context at all, and a core that needs about five milliseconds of it per
frame came out at two frames in five refreshes.

Both loops in it - the light frames and black frame insertion's dark ones
- now give the lock up around the latency wait, and around Present where
the runtime's multithread protection is on, with the helpers the frame
uses. Neither wait depends on pipeline state, and the copy or clear
between them is issued with the lock held as before.

Passes tools/mingw_syntax_check.sh (win32 gnu99 and the C89 lane). Not
run.
2026-09-19 21:17:36 +02:00
libretroadmin e5b109aed6 (Win32) The window title is set with a deadline, not an open-ended wait
A core driving a hardware context calls the frame callback from whichever
thread it renders on, and the frame updates the window title. SetWindowText
sends WM_SETTEXT, and a send does not return until the window's own thread
takes it off its queue -- but that thread is inside retro_run, waiting for
the core, and is not pumping messages. Each waits for the other. A PS2 core
presenting from its EE thread hangs at boot this way, with the frontend
thread parked in the core's own wait and the EE blocked in SendMessageW.

The same message with a timeout: the title is cosmetic and a frame that
cannot set it loses nothing, where a frame that blocks on it loses the
session. ABORTIFHUNG returns at once when the target is already known to
be stuck rather than waiting the deadline out. Both flags and
SendMessageTimeout predate NT 4, so the oldest target still builds; checked
at _WIN32_WINNT 0x0400, 0x0501 and 0x0600.
2026-09-19 21:12:21 +02:00
libretroadmin 89da82e3cc The video thread reads the frame's snapshot, and a check that says so
The null driver's present_last() read a setting the main thread was
writing, and TSan found it rather than anyone noticing by reading. Two
more were doing the same from frame(): xvideo and sdl2 took the OSD
message position out of the live settings while the video thread was
drawing with it. xvideo takes it from the video_frame_info_t its
caller already holds; sdl2 has two callers, frame() and the OSD poke,
so the position arrives as a pair of floats - the snapshot's from the
frame, the setting's from the poke, where the wrapper holds the main
thread inside the command and nothing can be writing.

tools/video_thread_state_check.py looks for the rest of the class
instead of waiting for the next one: it seeds from frame() and
present_last(), walks what they call, and fails on config_get_ptr(),
video_state_get_ptr() or runloop_state_get_ptr() anywhere it reaches.
It cannot follow a call through a function pointer or into another
file, so a clean run means no path it can follow, not no path at all.
The drivers for platforms with no video thread are listed with that
reason - a read that is latent today and a finding the day the
platform runs threaded - and an entry whose driver has stopped
reading fails too, so the list shrinks. It carries a self-test for
both directions and runs beside the gfx samples.
2026-09-19 21:07:30 +02:00
LibretroAdmin cb758f6733 (D3D11) Interface version 2: the frame does not hold the context through its waits
A D3D11 core on interface version 2 under threaded video ran at 48 fps
where it runs at 60 otherwise, with 16 ms to vblank and 34 ms worst.

Under version 2 the driver's frame takes the lock the core and the
driver share the immediate context with, and it took it for the whole of
the frame. The frame blocks in three places: the waitable swapchain's
latency wait at the top, the wait for vertical blank, and Present. The
first two do not touch the context at all, and holding the lock through
them kept the core off the context for most of a refresh, every refresh.

The lock is given up around the two waits and taken again after, which
marks the context used by the frontend as any other turn does. What the
core does to the pipeline state meanwhile cannot matter at either point:
the latency wait comes before the frame has bound anything, and the wait
for vertical blank after it has issued its last draw and flushed.

Present does touch the context. With the runtime's own multithread
protection on - ID3D10Multithread, which a D3D11 immediate context has
answered for since Windows 7 - the runtime serialises Present against
the core's calls itself, for as long as it needs the context, so the
lock is given up around a waiting Present as well. Protection is turned
on when version 2 is handed out; where that is not possible, Present
stays under the lock as before.

With no wrapper in front the frame runs on the core's thread inside the
core's own lock; giving up one level of a recursive lock there changes
nothing, as it should. Version 1 cores never reach any of this.

Passes tools/mingw_syntax_check.sh (win32 gnu99 and the C89 lane). Not
run.
2026-09-19 20:55:16 +02:00
LibretroAdmin ef32d1f4a2 (D3D11) libretro_d3d11.h version 2, in the driver and in the threaded ring
The contract went in with 357a049. This is the frontend's half of it.
Nothing changes for any core until one asks: version 2 is handed out
only to a core that negotiated it, and every existing core still gets
version 1 - under threaded video, the deferred context behind the proxy,
exactly as before.

Negotiation. GET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_SUPPORT answers
version 1 for the D3D11 type, and
d3d11_hw_interface_negotiated_version() (gfx/common/d3d11_hw_interface.h)
is where the driver and the ring both learn what the core asked for.

The lock. A critical section in the driver, recursive, which is what the
contract needs: with no wrapper in front the driver's frame runs inside
the video_refresh the core called with the lock held, and the frame
calls itself for black frame insertion. Only two of the driver's entry
points can run while the core is running - the frame, and the repeat of
the last one - so those two take it; everything else reaches the video
thread as a command the main thread waits for, and the main thread is
not inside the core while it waits. Taking it marks the context as used
by the frontend, which is what lock_context reports to the core and
clears.

The frame. With no wrapper, set_texture's texture is what the frame
reads, in place of whatever is bound at pixel shader slot 0. Under the
threaded ring the core gets the immediate context itself - no deferred
context, no proxy, no command list to replay - and at video_refresh the
driver copies the named texture into the slot's own: one call on the
context the core's thread holds the lock for, ahead of anything the core
does next. The slot's texture is replaced (a change of frame size) and
the video thread takes its reference to it under that same lock.

The header gains one paragraph: video_refresh is the frontend's use of
the context too, so a core that carries on after it within the same lock
treats the state as disturbed.

d3d11.c, video_thread_hw.c and runloop.c pass tools/mingw_syntax_check.sh
(win32 gnu99 and the C89 lane). Not run: it needs a core that asks for
version 2, which is the next change, and a Windows GPU.
samples/gfx/d3d11_hw_interface_v2 still runs the contract itself.
2026-09-19 20:40:15 +02:00
LibretroAdmin 357a049bf6 libretro_d3d11.h: version 2, where the core and the frontend take turns on the immediate context
A D3D11 device has one immediate context, and it cannot be used from two
threads at once. Version 1 hands it to the core and takes the frame from
whatever is bound at pixel shader slot 0, which works only while the
frontend draws on the core's thread. A frontend that presents from
another thread cannot share it under those rules; all it can do is give
the core a deferred context, and a deferred context records but cannot
read back - no Map for reading, no query results. A core that reads its
render targets back loses that silently, and has to be written around a
context whose state and object lifetimes do not behave like the one it
was written for.

Version 2 shares the immediate context by taking turns:

  lock_context / unlock_context
      whoever holds the lock has the context to itself and everything
      D3D11 can do with it, reading back included. lock_context returns
      true when the frontend has used the context since the core last
      released it, and the core then binds again whatever it relies on;
      false means the context is exactly as it left it. Recursive. A
      core may lock and unlock as often as it likes within a frame.
  set_texture(texture)
      the frame, named outright. The frontend is finished with it by
      the time video_refresh returns - one context orders everything -
      so there is no sync index, and the slot 0 convention is not needed.

context is always the immediate one in version 2. The struct is version
1's with members added at the end; RETRO_HW_RENDER_INTERFACE_D3D11_VERSION
stays 1 because existing cores reject anything else, and a core gets
version 2 only by asking through the negotiation interface libretro
already has (RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D11 is new
in libretro.h), exactly as for D3D12. How the lock is made is the
frontend's business: a lock of its own is enough, since both sides take
it for every use, so nothing here needs ID3D11Multithread or Windows 10.

This is the contract and a proof that it is sufficient; no driver
implements it yet and no core asks for it, so nothing changes for anyone
until they do.

samples/gfx/d3d11_hw_interface_v2 runs it on a real D3D11 device: a core
that draws through cached state, reads its own render target back every
frame and hands the texture over, against a frontend thread that takes
the lock and draws with its own target, shaders and viewport before
checking the frame it was given. 200 frames drawn, read back and seen.
A core that ignores lock_context's answer is the control and is caught
at once ("the core drew frame 1 and read back 0"); run.sh requires that.
Built with mingw-w64, run under Wine, added to the gfx samples lane,
which fails rather than skips if it finds no D3D11 device. The header
compiles as C89 and as C++.
2026-09-19 20:18:30 +02:00
LibretroAdmin 2903a3a9c9 video_null: present_last reads the frame's snapshot, not the live setting
ThreadSanitizer, threaded_video harness, present-repeat lane:

  data race gfx/video_driver.c:451 in video_null_present_last
    read  by 'ra-video'  video_null_present_last <- video_thread_loop
    write by main thread lane_present_repeat (threaded_video_test.c:373)

The null driver's present_last asked config_get_ptr() for
video_black_frame_insertion. Under the threaded wrapper that is the
video thread reading a setting the main thread writes, which is the
read the wrapper exists to keep off that thread; the lane writes the
setting to test black frame insertion and the two met.

The count now comes from the frame: video_null_frame keeps
video_info->black_frame_insertion, the snapshot the main thread took
for that frame, and present_last returns it plus one. frame and
present_last run on the same thread with or without the wrapper, so
there is nothing left to race, and it is how the real drivers learn the
value. The lane still sees what it checks - it runs frames after
changing the setting, so the repeat that follows carries the new count.

Passes tools/mingw_syntax_check.sh. The harness was not rebuilt here
(a full sanitizer build of the frontend); the CI lane that reported
this is the check.
2026-09-19 19:52:12 +02:00
LibretroAdmin 00e0963ec0 (D3D12) libretro_d3d12.h version 2, in the driver and in the threaded ring
The contract went in with 75f55ed. This is the frontend's half of it.
Nothing changes for any core until one asks: version 2 is handed out
only to a core that negotiated it, and every existing core still gets
version 1 and the copy that goes with it.

Negotiation. GET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_SUPPORT answers
version 1 for the D3D12 type. d3d12_hw_interface_negotiated_version()
(gfx/common/d3d12_hw_interface.h) reads what the core then set and gives
min(what it asked for, 2), or 1; the driver and the ring both ask there,
so they cannot disagree.

The driver, with no wrapper in front. Its frame runs inside
video_refresh and reads the core's texture there, so there is one sync
index. set_texture_fenced queues a Wait on the core's fence ahead of the
frame's command list - a wait on the GPU's timeline, none on the CPU -
and a fence signalled behind that list is what wait_sync_index waits.

The threaded ring. A version 2 slot is the core's own texture: publish
records and submits nothing on the core's thread, which is where version
1's cost was (a command list, two barriers, a copy, a submit and a
signal, every frame). The video thread gives the driver the texture and
the core's fence through hw_ring_install, which D3D12 now implements;
the driver queues the Wait ahead of its frame and reads the texture
where it used to read the slot's copy. The slot holds a reference to the
texture and the fence until it is handed over again. A core that calls
version 1's set_texture on a version 2 interface gets version 1's path
for that frame.

One thing version 2 cannot share with the copy path. A slot's fence says
the video thread has driven the driver with the slot and the GPU is
done; it says nothing while a frame is queued and not yet claimed, or
claimed and still being recorded. With a copy per slot that window is
harmless - the copy is replaced by a newer one. With version 2 the slot
is the core's texture, and a core let back in then would draw into what
the frame about to be recorded reads. So for version 2 slots the wait
also covers the window: until no queued frame names the slot, on the
condition the video thread already broadcasts when it claims a frame and
when it finishes one. Other APIs' waits are untouched.

d3d12.c, video_thread_hw.c and runloop.c pass tools/mingw_syntax_check.sh
(win32 gnu99 and the C89 lane). Not run: it needs a core that asks for
version 2, which is the next change, and a Windows GPU to run it on.
samples/gfx/d3d12_hw_interface_v2 still runs the contract itself.
2026-09-19 19:50:07 +02:00
LibretroAdmin 75f55ed57d libretro_d3d12.h: version 2, where each side says when it is done with the frame
Version 1's whole handoff is set_texture(texture, format). It does not
say when the texture is complete or when the core may draw into it
again, so the only safe thing a frontend can do is copy it before
video_refresh returns - and one that presents from another thread has to
record and submit that copy on the core's thread. That is the cost the
threaded video statistics show for a D3D12 hardware core next to a
Vulkan one (about 23 us a frame against 5): not D3D12, whose queues are
free-threaded and whose fences do what Vulkan's semaphores do, but an
interface that exposes none of it.

Version 2 adds what the Vulkan interface has had all along:

  get_sync_index / get_sync_index_mask
      the core keeps one present texture per index and is told which to
      draw into
  wait_sync_index
      blocks until the frontend has finished with the texture last
      handed over for the current index; until then it is not the core's
  set_texture_fenced(texture, format, fence, value)
      the frame, complete once the fence reaches the value; the frontend
      does not read it before then, on whatever queue or thread it reads

The struct is version 1's with members added at the end, so one object
serves both, and RETRO_HW_RENDER_INTERFACE_D3D12_VERSION stays 1 because
existing cores compare against it and reject anything else. A core gets
version 2 only by asking, through the negotiation interface libretro
already has: RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D12 is new
in libretro.h, and retro_hw_render_context_negotiation_interface_d3d12
carries the highest version the core can use. An older frontend answers
GET_..._NEGOTIATION_INTERFACE_SUPPORT with 0 for the type and the core
carries on with version 1, having asked for nothing.

This is the contract and a proof that it is sufficient; no driver
implements it yet and no core asks for it, so nothing changes for
anyone until they do.

samples/gfx/d3d12_hw_interface_v2 runs it: a core and a frontend that
presents from its own thread, sharing a device and a queue, with the
handoff above and no copy on the core's thread. The frontend is slower
than the core, so the core laps it and has to block in wait_sync_index;
every frame must reach the frontend holding the frame number it was
published with. 240 of 240 do. A core that skips wait_sync_index is
built in as the control and is caught on its first lap ("index 0 should
hold frame 0, holds frame 237"); run.sh requires that. Built with
mingw-w64, run under Wine with vkd3d over Mesa's software Vulkan driver,
and added to the gfx samples lane, which fails rather than skips if it
finds no D3D12 device. The header compiles as C89 and as C++.
2026-09-19 19:24:51 +02:00
libretroadmin 6af2e74598 Don't mention specific cores 2026-09-19 19:22:31 +02:00
libretroadmin 79b7ddb9cf d3d11_hw_ring: the runner the workflow calls, and the rule that swallowed it
.gitignore carried a bare run.sh, which git matches at every depth, so
the script the gfx samples job calls could never be added and the job
has been failing on a file that is not in the tree. The rule is for
the checkout's own run.sh and now says so.

run.sh builds the oracle and the deferred proxy it exercises with
mingw-w64 - dxguid and uuid carry the interface GUIDs the proxy names
- brings up an Xvfb display when the job has none, and runs the three
cases the test documents: the ring and the immediate context must
pass, and the ring as it used to be, resetting the context every
frame, must fail. A build where that last one passes has lost the
regression the sample exists for.
2026-09-19 19:10:45 +02:00
LibretroAdmin 89912eb6c9 (D3D12) HDR: the frame's trip from the back buffer to the swapchain is opaque
The PS2 core on D3D12 with its hardware renderer showed a black picture
with HDR on: audio, input, widgets, statistics and the overlay all fine.
With HDR off it showed its picture. With the core's software renderer it
showed its picture either way.

With HDR on, and a menu or an overlay up, the frame is drawn into
chain.back_buffer and then composited onto the swapchain image. That
composite used pipes[VIDEO_SHADER_STOCK_HDR], whose blend state is
SRC_ALPHA / INV_SRC_ALPHA, and the HDR shader returns the sampled alpha
with the colour. So the frame was blended over the freshly cleared
swapchain image at whatever alpha the core's texture holds.

For a software frame that is 1: XRGB8888 has no alpha to read. A
hardware core hands over its own render target. The PS2 core's holds the
guest's alpha - the GS keeps destination alpha in the frame buffer, and
for most of a frame it is zero or 0x80 - so its picture was composited at
zero or half over black. The software renderer's output is opaque, which
is why the same core showed a picture in that mode; and the overlay the
reporter runs is what put the frame through the back buffer at all.

The composite now uses VIDEO_SHADER_STOCK_NOBLEND_HDR: same shader, same
target format, blending off. There is nothing under the frame to blend
with - the swapchain image is cleared immediately before - and the SDR
path has always drawn the frame with blending off. The second use of
STOCK_HDR further down, which lays the back buffer under the menu and
widgets, is left as it is.

Found with the D3D12 debug layer forced on from dxcpl, which showed the
core's own draws to be clean, and the reporter's HDR on/off and
software/hardware results. Passes tools/mingw_syntax_check.sh. Not run:
no HDR output here.
2026-09-19 19:09:33 +02:00
LibretroAdmin b178b90772 (D3D12) Overlays pick the HDR blend pipeline when the back buffer is HDR
With HDR on, the D3D12 debug layer reports, for every overlay image of
every frame:

  ID3D12CommandList::DrawInstanced: The render target format in slot 0
  does not match that specified by the current pipeline state.
  (pipeline state = R8G8B8A8_UNORM, render target format =
  R10G10B10A2_UNORM) [RENDER_TARGET_FORMAT_MISMATCH_PIPELINE_STATE]

Thousands in a ten-second capture with an input overlay loaded, and
nothing else.

d3d12_render_overlay() binds sprites.pipe_blend unconditionally. That
pipeline is built for an R8G8B8A8 target; the overlay is drawn onto the
swapchain's back buffer, which under HDR is R10G10B10A2 or
R16G16B16A16_FLOAT. A draw whose pipeline disagrees with the bound render
target is invalid, and what it does without the debug layer is up to the
driver. Every other sprite draw in the file - blend_begin, blend_end,
the font, the menu sprites on either side of the overlay call - already
picks the _hdr pipeline by testing chain.current_rt_format. The overlay
now does the same. It is drawn between the same root CBV and sprite
setup the menu sprites use, so PSMainHDR sees the state it expects.

Passes tools/mingw_syntax_check.sh. Not run: no HDR output here, and
Wine has none to offer.
2026-09-19 19:09:29 +02:00
libretroadmin 86b3de3205 CI: the samples no other workflow runs
A sample that builds, runs and asserts something, and that no
workflow names, is a test nobody runs. font_lifecycle sat that way
long enough for its expectation to drift away from the font driver it
was written against, and it is not alone: fifteen others are runnable
and unclaimed, from the audio pipeline's format and layout oracles to
the overlay alpha-flag and viewport seqlock ones.

This job takes whatever is left over, under the address and undefined
sanitizers with leak detection on. The list is computed from the
sample tree and the workflows rather than written down, so a new
sample is covered by existing rather than by being remembered, and a
sample that gets a workflow of its own drops out of here on its own.
Samples needing a library the runner does not carry are skipped by
name; their own workflows install what they need.
2026-09-19 19:07:26 +02:00
libretroadmin 0acf4dbe11 font_lifecycle: a rebuild marshals whatever the hint says, and the sweep asks make what it can run
The sample held the older rule - that a font made without a threading
hint rebuilds on the calling thread - and nothing ran it, so it sat
there disagreeing with the code. A rebuild goes through the video
thread whenever the wrapper is up, whatever thread made the font: a
font size change rebuilds the OSD font from the settings path on the
main thread, and a GL backend making its context current there while
the video thread holds it is an X BadAccess. The hint decides where a
font is created, not where it is rebuilt, and the sample now says so
and runs beside the other gfx samples.

The sweep found it, but only after it stopped inventing failures:
"does this sample have a check target" was a grep for check:, which a
comment satisfies, so samples whose runnable target is called
something else were sent to a rule that does not exist. make -n
answers the question properly.
2026-09-19 18:58:10 +02:00
LibretroAdmin 403f441f95 menu: fold the console/mobile widget scale range and handler into its row
The console/mobile Graphics Widgets Scale Override row is a plain
S_FLOAT row, which has range and OK handler slots, but it left them
empty and relied on two builder lines after ADD_DESC to supply the
0.2-5.0 range and setting_action_ok_uint. That dependency is what
broke in fb4a17e562 (fixed in #19584): the builder lines moved into
the desktop branch and the console row was left with nothing.

Put the range and handler in the row itself, the same way the
windowed row already does, and move the two builder lines back into
the desktop branch where only the fullscreen LV row still needs them
(the LV float grammar has no range or handler slots). The comment
explaining that is restored.

No behavior change: SDESC_RANGE_MINMAX with 0.2/5.0/0.01 is the same
range, enforced at both ends, that add_range(..., true, true) applied,
and the handler is the same. Desktop rows are untouched.
2026-09-19 18:54:35 +02:00
Artem Lytkin 365b4ba082 menu: restore widget scale override range on console and mobile (#19584)
fb4a17e562 moved the OK handler and the 0.2-5.0 range of the widget
scale row into the desktop branch, so on console and mobile builds
Graphics Widgets Scale Override was left with no range and no OK
action and could not be changed. Apply them after either row again.
2026-09-19 18:55:39 +02:00
LibretroAdmin fa4879f8fe (D3D12) C89: hoist loop counters out of for-init in the HDR setters
Six d3d12_set_hdr_* setters declared their counter inside the for
statement (for (unsigned i = 0; ...)), which is C99 and does not build
as C89. Declare it at the top of the block instead. No functional
change.
2026-09-19 18:47:25 +02:00
LibretroAdmin 7364d29509 (D3D11/D3D12) Screenshot: keep the read window inside the backbuffer
read_viewport and read_viewport_hdr clamp the viewport width and height
to the full output size, but never check x + width or y + height. The
viewport origin is user-controlled (custom aspect ratio), so a viewport
pushed off the right or bottom edge made the CPU copy loops - and the
shared dxgi_hdr_readback_* decoders, and the GPU HDR readback loops -
read past the end of each source row and, on the last rows, past the
end of the mapped staging/readback memory.

Add dxgi_readback_clamp_window() and call it in all four readback
entry points, against the real source size (the staging desc on D3D11,
the swapchain size on D3D12). Width and height double as the stride and
row count of the caller's buffer, which task_screenshot clamps the same
way, so they are left alone and the window is slid back inside the
surface instead. Vulkan already clamps its copy extent; the D3D drivers
did not.
2026-09-19 18:47:21 +02:00
LibretroAdmin 6929c9048e (D3D11) Threaded video: the core's deferred context keeps its state across frames
A D3D11 hardware core under threaded video showed one frame and then
black, with widgets, overlays and statistics still drawn over it. With
threaded video off the same core showed its picture. Reported on the PS2
core.

The hardware ring gives the core a deferred context and closes its
recording at every video_refresh with FinishCommandList(..., FALSE, ...).
FALSE resets the deferred context to the default state each time. A core
written against the immediate context does not expect that: what it
binds stays bound there, because the frontend's own frame does not touch
those slots, so it binds some things once and never again. The PS2 core
binds its vertex-expansion buffer to VS slot 0 once, at device creation,
and the state it rebinds after each present does not include it. From
the second frame on that slot was empty, every sprite, line and point
collapsed to nothing - the screen and depth clears games draw as sprites
among them - and the picture was black.

TRUE keeps the deferred context's state across lists, which is how the
immediate context the core was written for behaves. The value lives in
d3d11_deferred_proxy.h as D3D11_HW_RING_KEEP_CONTEXT_STATE so the test
below uses what the driver uses.

samples/gfx/d3d11_hw_ring runs the ring's sequence - record through the
real proxy, take the texture at PS slot 0, FinishCommandList, replay on
the immediate context, copy - against a core that binds through a cache,
rebinds a fixed set after every present and has one binding it makes
once. It draws a different colour each frame over a black clear and
reads the frontend's copy back. Built with mingw-w64 and run under Wine,
where wined3d implements deferred contexts over OpenGL: the ring and the
immediate context show all four frames; the ring with FALSE shows the
first and then black, and run.sh requires that it still does. Added to
the gfx samples lane, which already has mingw and Wine. Nothing on Linux
exercised the D3D11 ring before this.

d3d11.c passes tools/mingw_syntax_check.sh. Not run on real Windows.
2026-09-19 18:39:20 +02:00
LibretroAdmin ba12bd21cf (D3D12) Screenshot: propagate CPU HDR decode failure
d3d12_gfx_read_viewport() ignored the return value of
dxgi_hdr_readback_to_bgr24() on the CPU HDR fallback path and always
returned true, so a failed decode was reported as a successful
screenshot with an unwritten buffer. D3D11 already returns false here;
make D3D12 match.
2026-09-19 18:37:19 +02:00
LibretroAdmin 27114685e1 (D3D11) HDR screenshot: clear staging MiscFlags, init BackBuffer
Follow-up to #19585. d3d11_gfx_read_viewport_hdr() builds its staging
desc from the swapchain backbuffer desc the same way the SDR path did,
but never cleared MiscFlags. Backbuffer misc flags are not valid on a
D3D11_USAGE_STAGING texture, so strict drivers (seen on Qualcomm
Adreno X2-90 for the SDR path) reject CreateTexture2D. The HDR path
already NULL-checks the result so it did not crash, but the HDR
screenshot silently failed.

Also initialise BackBuffer to NULL so the !BackBuffer check after
GetBuffer is not testing an uninitialised pointer when GetBuffer
fails.
2026-09-19 18:34:54 +02:00
Quan Wei e6b62c0e81 Handle D3D11 screenshot staging creation and map failures (#19585) 2026-09-19 18:35:46 +02:00
LibretroAdmin a47f2cd0dc threaded_video harness: the dupe lane does not free a frame the cache still holds
The dupe lane added with 8ae3e66882 failed the ASan lanes:

  heap-use-after-free ... READ of size 64
    #1 video_thread_frame gfx/video_thread_wrapper.c
    #3 video_driver_cached_frame
    #6 run_frames
    #7 lane_dupe_under_wrapper
  freed by thread T0 here:
    #1 lane_dupe_under_wrapper

The test's fault, not the wrapper's. video_driver_frame() keeps the
pointer it is given as the cached frame - borrowed, exactly as it
keeps a core's framebuffer - and the menu re-presents it on the next
frame it draws. The lane malloc'd its test pattern, pushed it, freed
it, and then ran two more frames with the menu up, which went back
for it.

The pattern is now a static buffer, so what the cache borrows stays
valid the way a core's framebuffer does, and the lane invalidates the
cached frame before it hands back, so the lanes after it do not find a
test pattern there.

I had only run the lane on a plain build, which is how this got
through. Run this time the way CI runs it, on trees built for it:
SAN=address (ASan+UBSan) and SAN=thread, CYCLES=2, all lanes pass.
2026-09-19 17:22:52 +02:00
LibretroAdmin 050200bb62 settings: regenerate settings_table.reference
tools/settings_check.py table --check fails on master: two settings
were added without the reference being regenerated alongside, so the
"Settings table drift" CI job reports drift for every change that
follows, whatever it touches.

  bool  audio_fastforward_callback
  uint  menu_thumbnail_preview_threads

Regenerated with

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

Those two rows, in each of the three configurations, are the whole
difference. table --check, unity (desktop and mobile) and
settings_migrate_selftest.py all pass.
2026-09-19 17:00:12 +02:00
LibretroAdmin 6081dd9790 configuration: audio_asio_output_channel is an ASIO setting, not a WASAPI one
A Windows build configured with --disable-wasapi did not compile:

  configuration.c: error: 'struct <anonymous>' has no member named
  'audio_asio_output_channel'

The field was declared in settings_t inside the #ifdef HAVE_WASAPI
block, next to audio_wasapi_sh_buffer_length. Everything that uses it -
its row in settings_def_audio_asio_action.h, the descriptor and
configuration tables that row expands into, the menu representation in
menu_setting.c, and asio.c itself - is under HAVE_ASIO, and ASIO and
WASAPI are switched independently. With ASIO on and WASAPI off the row
was emitted and the field was not there.

It gets a HAVE_ASIO block of its own. Builds with both drivers, or
with neither, see no difference.

Checked with a mingw cross build's own compile lines, HAVE_ASIO
defined and HAVE_WASAPI not: configuration.c fails before and compiles
after; menu_setting.c and asio.c compile.
2026-09-19 17:00:08 +02:00
LibretroAdmin 8ae3e66882 video_thread: a duped frame reaches the driver as NULL, not as an old frame
With threaded video on, the ffmpeg core ghosted on Vulkan and D3D12 -
every frame doubled with an older one - and on D3D11 faded to grey and
back. gl and glcore were clean, because there the core renders with
OpenGL itself and never sends pixels at all.

A core with nothing new calls video_refresh with NULL, and the driver
repeats the frame it has. The ffmpeg core does that for every display
refresh that falls between two video frames: every other call for a
30 fps clip on a 60 Hz display.

video_thread_frame() treated that push like any other. It picked a
free ring slot, copied nothing into it because there was nothing to
copy, and published it. The video thread then gave the driver that
slot's buffer as the frame. What the buffer held was whatever had last
been put there: the frame from two pushes before, or - when the real
frames all arrive zero-copy in the other slot, as the ffmpeg core's
now do - something older still, down to the 0x80 fill the slots are
allocated with. The driver uploaded it and showed it as new. So the
display went new, old, new, old at the refresh rate.

A slot now records that its push carried no pixels, and for such a
slot the video thread hands the driver NULL, which is what the core
sent and what every driver already gets without the wrapper. The
pixel-format conversion and the video filter are skipped for it, as
they have nothing to work on. A hardware frame the ring could not
publish takes the same route; the comment there already said it was
to be treated as a dupe.

Regression test: a new lane in the threaded_video harness pushes
pixels, NULL, pixels, NULL through the wrapper with the driver's
frame() interposed, and requires the driver to see exactly that. On
the old code it fails with the driver given stale pixels for every
dupe; with this change it passes.

Also run on a real build under Xvfb, ffmpeg core on the software path:
Vulkan and gl1, threaded video on and off.
2026-09-19 16:40:57 +02:00
LibretroAdmin a38053c8f8 glcore: gl3_t::flags is 32 bits, so the flags above bit 15 exist
Loading a hardware-rendered OpenGL core (the ffmpeg core, any GL core)
on Windows with the glcore driver and threaded video on crashed at
load: a call through NULL out of the core's context_reset(), with

  [WGL] wglMakeCurrent(core context) failed: 0x000000aa.

just before it. 0xaa is ERROR_BUSY: the core's context was current on
another thread. It was - the video thread had taken it.

gl3_t::flags is a uint16_t, and two flags are defined past the end of
it:

  GL3_FLAG_HW_RING_EXPECTED = (1 << 16)
  GL3_FLAG_OVERLAY_BORROWED = (1 << 17)

`gl->flags |= GL3_FLAG_HW_RING_EXPECTED` therefore stores nothing, and
every test of either flag is false, with no warning from any compiler.

HW_RING_EXPECTED is what gl3_core_context_is_mains() reads between
init and the ring coming up, to keep the video thread from taking the
core's context while the main thread is about to. With the flag lost,
the bind at the end of gl3_init() ran, the video thread left init
holding the core's context, and the main thread's wglMakeCurrent() for
it failed. With no context current, every GL function the core then
looked up came back NULL, and it called the first.

OVERLAY_BORROWED is what stops glcore deleting overlay textures that
belong to the overlay pack (load_textures). With the flag lost, glcore
deleted textures it had only been lent.

The field is now a uint32_t, as gl2's already is. Nothing else
changes: every other flag keeps its bit.

Found with a mingw build under wine, which fails the same way as a
real Windows box: before, the 0xaa error and no frames; after, the
core's context goes current on the main thread, 117 frames are pushed
and the process exits 0. glcore on Linux unchanged, threaded video on
and off.
2026-09-19 16:27:34 +02:00
LibretroAdmin 7a533d7d68 libretro-common: fastjmp_buf carries the alignment its Win64 stores need
The Win64 fastjmp_set saves xmm6-xmm15 with movaps, which faults on an
address that is not a multiple of 16. The header says the buffer is
"16-byte aligned for the movaps stores" in a comment over a plain
unsigned char array, whose alignment is 1. A file-scope static gets away
with it because compilers put a 240-byte static on a 16-byte boundary
anyway; a fastjmp_buf that is a struct member after a char sits at +1,
and the first fastjmp_set dies at "movaps %xmm6, 0x50(%rcx)".

The member is declared aligned to 16 with the MSVC or GNU spelling, both
valid in C89, so alignof(fastjmp_buf) is 16 on Win64. The System V and
AArch64 forms store through general-purpose moves and stp and are
unchanged, as is the setjmp fallback.

fastjmp_test gains that case: a buffer placed after a char, its address
checked on Win64, and a set and jump through it everywhere. Linux has no
aligned stores, so the Linux samples job could never have seen this; the
MSYS2 job in Cross-libc-vfs-samples builds and runs fastjmp_test too
now, under a second, and triggers on the fastjmp paths. Built with
mingw-w64 and run under Wine: the new case fails on the old header at
the address check and then faults, and passes on this one.
2026-09-19 16:14:13 +02:00
Craig 161c36628e windows: switch to new gcc 16 docker (#19579) 2026-09-19 16:04:34 +02:00
LibretroAdmin d0b1630ff9 video_thread: the ring's GL context is given back at free, not dropped
Closing content with threaded video on, the glcore driver and an OpenGL
hardware core (the ffmpeg core is the easy one to hit it with) killed
the process on X11:

  X Error of failed request:  BadAccess
  Major opcode of failed request:  150 (GLX)
  Minor opcode of failed request:  26 (X_GLXMakeContextCurrent)

The hardware ring takes the core's GL context on the main thread at
init and has to give it up there at free - only the thread a context
is current on can release it. video_thread_hw_free() runs on the video
thread, inside CMD_FREE, so it hands that release to the main thread
through video_thread_call_on_waiter().

video_thread_call_on_waiter() finds the wrapper through
video_thread_thr_capture, and video_thread_free() clears the capture
before it sends CMD_FREE. So by the time the ring asked, there was no
wrapper to be found, and the function returned without calling
anything. The core's context stayed current on the main thread, and
gl3_deinit_hw_render(), a few lines later on the video thread, bound
it: BadAccess on GLX, which Xlib answers with exit(1). On WGL the same
bind is a failed wglMakeCurrent, the context cannot be deleted while
another thread holds it, and the main thread goes into the next
session still holding a dead context.

video_thread_free() now names the instance it is freeing for the
length of CMD_FREE, and video_thread_call_on_waiter() falls back to it
when the capture is gone. The capture itself still clears first, so
the font and texture entry points stop taking the instance exactly
when they did. And with no wrapper to be found at all the call is made
in place, as it is in a build without threads, instead of vanishing.

Verified on a real build under Xvfb (Mesa): glcore + threaded video +
ffmpeg core exits 1 with the X error before, exits 0 after.
2026-09-19 15:55:46 +02:00
LibretroAdmin 6c9817e510 gl2: a hardware core under the threaded wrapper always gets its context
Loading a video in the ffmpeg core with the gl driver and threaded
video on crashed at load. On Windows it is a call through NULL out of
the core's context_reset(), from drivers_init(); on Linux a segfault in
the same place, after

  [Video] Threaded video: the core's GL context could not be taken on
  the main thread; the core will have no context.

Under the wrapper an OpenGL core renders on the main thread while the
driver draws on the video thread, so the core cannot borrow the
driver's context as it does unthreaded. It needs one of its own that
shares objects with the driver's: the shared context, which the
hardware ring makes current on the main thread before context_reset()
runs. gl2_hw_ring_context_new() refuses when there is no shared
context to take.

gl2 only ever made a shared context when the setting was on or the
core had asked for one with SET_HW_SHARED_CONTEXT. A core that did
neither - the ffmpeg core is one - was therefore sent into
context_reset() with no context current at all. On WGL every function
it looks up without a context comes back NULL, and it called the
first one.

glcore has always forced the shared context for hardware cores. gl2
now does the same whenever the wrapper is running and the ring is
allowed for the session, which is exactly when it cannot work without
it. Unthreaded behaviour is unchanged.

Verified on a real build under Xvfb (Mesa), gl + threaded video +
ffmpeg core: crashes at load before, plays and exits 0 after; threaded
off unchanged.
2026-09-19 15:55:41 +02:00
kaysedwardsandLibretroAdmin e8f9fec4c7 Support More Video Files (#19583)
* I'm pleased to release a new variant of my changes
to the FFMPEG core. I have, I believe, fixed the
issues with the current code as well as my old
version. The core should now play files containing
many more video codecs--including HEVC and AV1 files.

The issue stemmed from how avcodec_send_packet
and avcodec_receive_frame must be mutually looped
when using the modern FFMPEG interfaces; the current
code essentially disregards the returned value so
does the wrong thing in certain scenarios.

I've also changed the `seek_adjust` function slightly
so that the time adjustment is nearer to exactly five
minutes for the common input.

I've also cleared the SEEK_L2_STR and SEEK_R2_STR
variables in a few more places to fix a bug where
seeking could get stuck on the wrong adjustment.

I've also fixed the description of the controls;
they are currently misreporting a 60 second seek
instead of the implemented 30 seconds.

A few more bugs, unfortunately, remain: the audio
decoder has a bug not unlike the bug in the video
decoder; changing the audio to a stream having a
different rate will increase or decrease the speed
of the video being played; the implementation is
somewhat fragile in the face of broken files in
that stuttering can accumulate and even crash
the decoder.

A note on reporting bugs: please, before you report
a bug with a certan file formats, run your sample file
through FFMPEG to make sure the file itself contains
no errors. As I mentioned above, the implementation
is somewhat fragile; the core, as is, really needs
files that contain no errors for proper function.

* ffmpeg core: review fixes for the send/receive decode loop change

Four things found reading the change:

* A comment in seek_frame() picked up half of another line in an
  edit ("taken us to thestrength without using"). Put back.

* select_decoder() cleared FF_THREAD_FRAME with ^=. It works there,
  because the bit was set two lines up, but it is a toggle, not a
  clear, and the next edit that moves the assignment turns it into
  a bug. Use &= ~.

* sws_worker_thread() now builds its scaler from the decoded frame's
  own size and format instead of the container's, which is the right
  thing, but it means a frame from a damaged file can be one that
  sws_getCachedContext() refuses - and the NULL went straight into
  sws_setColorspaceDetails() and sws_scale(). Check it, log it, and
  still finish the slot so the ring keeps its order and the main
  thread is not left waiting for a frame that never completes.

* The rewrite left two things behind that now warn: decode_thread()
  still computed a frame_size nothing takes any more, and
  video_buffer_has_open_slot() lost its only caller. Both removed.

* ffmpeg core: software video path stops copying frames it does not have to

A frame on the software path (any driver that is not GL - Vulkan,
D3D, Metal) was copied twice after the scaler was done with it: row
by row out of its ring slot into a buffer of the core's own, and then
by the frontend out of that buffer into its texture. At 3840x2160
that is 33 MB a copy, 60 times a second.

The first copy bought nothing. The frame is already sitting in a slot
in the layout retro_video_refresh_t takes, so now the slot is what
the frontend is given.

The second is what RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER
is for. The core asks for the frontend's framebuffer every frame, as
the API requires; a driver that has one (Vulkan, D3D12) hands out the
mapped texture it draws from, the frame is copied into that, and the
frontend's own copy does not happen. One copy from scaler to GPU
instead of two. Where the frontend has nothing to offer, or offers a
format that is not XRGB8888, the slot is passed directly and it is
one copy that way too.

Showing a frame out of its slot means the slot cannot go back to the
decoder the moment it has been shown: the frontend may come back for
the last frame it was given (a paused menu redraws it), and by then
the decoder would be halfway through writing the next picture into
it. So the slot is held until the frame after it is due. Holding is
simply not opening - the slot stays finished at the tail, where the
decoder cannot go.

video_buffer_clear() (a seek) takes every slot back whether it is
held or not, and the same index can be finished again with a frame
nobody has seen by the time the holder lets go. The ring now counts
its clears, and a held slot is given back only if none happened since
it was taken; otherwise releasing it would drop that frame and push
the tail past it.

The software path's ring gets a fifth slot, so the decoder still has
four to run ahead with. That slot is the memory the temporary buffer
used to be: the buffer is now only allocated where it is still used,
which is the GLES texture upload.

Also checks the slot it is handed is not NULL, which the old code
did not: a clear between the wait and the take left ctx unset and
the next line read ctx->pts.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-09-19 15:40:54 +02:00
libretroadmin 0d7a5b4b1c The sample sweep runs what it builds, and two things that found
tools/sample_link_check.sh linked every sample a change touches and
called that a check. A sample that builds and passes plainly can
still be the one holding a leak or a use-after-free, and only the
sanitized run says so, so a sample with a check target is now built
and run the way its CI job runs it: address and undefined sanitizers,
leak detection on. Both modes go through one routine - the --all path
had a loop of its own, which is how the full sweep went on linking
while the selected one checked. A sample that cross-builds a Windows
target is built and not run, since this host cannot run it.

Two things it found on the first pass. pipeline_clocked builds a sinc
resampler per run through retro_resampler_realloc and never released
it, leaving seven behind and 232 KB with them; the teardown releases
it as audio_driver_deinit does. And input_overlay_textures matched a
page's entry to the pack's unique image by the pixel buffer they
share, which is NULL on both sides once the pixels are released - and
NULL matches the first image every time. A released buffer matches
nothing now, so the pack is declined and reloaded from its path
instead of drawing one image everywhere.
2026-09-19 15:23:12 +02:00
LibretroAdmin 52babd3fef qb: only enable XCB when x11-xcb is present
HAVE_XCB was enabled by the libxcb check alone, but the code it gates
(gfx/common/vulkan_common.c) includes X11/Xlib-xcb.h and calls
XGetXCBConnection, and Makefile.common links -lX11-xcb. Those come
from the separate x11-xcb package (libx11-xcb-dev on Debian/Ubuntu),
so a box with libx11 + libxcb but no x11-xcb failed with:

  gfx/common/vulkan_common.c:30:10: fatal error: X11/Xlib-xcb.h:
  No such file or directory

Check for x11-xcb in configure and turn HAVE_XCB off when it is
missing, and link via X11_XCB_LIBS instead of a hardcoded flag.

The X11 Vulkan context only ever creates an XCB surface, so build and
register gfx_ctx_vk_x only when HAVE_XCB is set rather than shipping a
context that cannot create a surface.
2026-09-19 15:07:28 +02:00
LibretroAdmin df0f7e7635 CI: a test that runs retroarch and looks at the overlay on the screen
Every overlay test in the tree stubs the driver or builds the pack by
hand, and every one of them passed while an enabled overlay covered
the whole screen. A person with threaded video off saw it in a second.
This test does what that person did.

samples/input/overlay_on_screen/overlay_on_screen_test.py takes a real
retroarch binary, starts it under Xvfb on Mesa's software GL with the
menu up and an input overlay enabled, grabs the root window with xwd
and reads the pixels - once with video_threaded off, once with it on.
The overlay is made on the spot: two solid magenta squares, top left
and bottom right, on a full-screen page at full opacity; magenta is no
colour the menu draws. It requires that

- the middle of each square is magenta (the overlay is up, in place);
- the middle of the screen is not (no image is stretched over it);
- magenta covers about the area of the two squares (8% here).

Against the previous commit's parent it fails in both modes with
"overlay covers 100.0% of the screen, its two squares are 8.0%"; with
the fix it passes at exactly 8.0% in both.

New workflow CI Linux overlay on screen: a slim X11 + OpenGL build
(shader compilers, the big menu drivers, networking and codecs off)
behind ccache, then the test, some twenty seconds. It runs when the
overlay frontend, the loader, gfx_surface, the video driver core, the
threaded wrapper or gl2 change.
2026-09-19 13:28:05 +02:00
LibretroAdmin 13c652060e Overlays: "has an image" is asked of the size, not of pixels that were released
An enabled input overlay covered the whole screen: every image of the
page drawn full-screen, one over the other (sonninnos; reproduced here
on the gl driver under Xvfb, threaded video off AND on).

The loader does not give the pack private image structs. For the first
overlay::image or overlay_desc::image that names a file,
task_overlay_load_image_texture() registers that struct's ADDRESS as
the pack's unique image, so input_overlay::images[] points into the
pages. Since 86fbf34e01 the pack's pixels are released once the driver
has the textures, with image_texture_free(images[i]) - which therefore
cleared .pixels inside the descs themselves. And "does this desc have
an image" was asked of .pixels, in four places: the page's and the
descs' vertex_geom, the movable-desc update, and the pressed-button
alpha. The very descs that had just been given textures answered no,
their geometry was never sent to the driver, and the driver drew each
image where a page load leaves it: over the whole screen.

e9468dedf8 moved that release later and kept it, and said of these
structs that they were "copies ... only ever tested ... and are left
to say so". They are not copies; that comment was wrong, and no test
could tell, because overlay_textures_test built its pack by hand with
private image structs.

- OVERLAY_HAS_IMAGE() asks the width. A desc or page without an image
  is calloc()ed and has none; one with an image keeps it for good
  (input_overlay_drop_pixels puts the sizes back after the free). The
  four tests use it.
- With nothing reading .pixels as a flag any more, every copy of a
  released pointer is cleared - the pages' load_images as before, and
  now the page and desc image structs that share the file - both when
  the pixels are dropped and when a surface in flight adopts them.
  Nothing is left pointing at freed memory.
- overlay_textures_test lays its pack out as the loader does: descs
  with images, images[] pointing into them, later users holding
  copies. A new "has image" lane requires every desc to still report
  its image once the page has gone as textures, and to hold no stale
  pointer. With the old question put back it fails for the four descs.
2026-09-19 13:27:59 +02:00
LibretroAdmin 946ef57971 Overlays: audit of the video drivers' overlay paths (frontend, GL, Vulkan, D3D9-12, Metal)
One audit, one commit. Every item below was found by reading the
overlay code of each driver after the gl2 blackout (78fda8c573); what
could be run here was, and what could not is said at the end.

Frontend
- input_overlay_load_active() always went on to set a page's per-image
  alpha and geometry after loading it. Twice the page may not be in
  the driver: load() can fail - its answer was thrown away - and a
  pack with neither textures nor pixels left is never handed to load()
  at all. The setters then ran with the new page's indices against
  whatever the driver held before. input_overlay_load_page() now
  answers none / pixels / textures, counts a load() that returned
  false as none, and the geometry is skipped for none. Every driver's
  load() ends in true on success and the threaded wrapper passes the
  answer back.

gl1, gl2, gl3 (glcore), vulkan
- 7661ab06e7 bounded vertex_geom and tex_geom in the three GL drivers
  and missed set_alpha: NULL overlay_color_coord with no page loaded,
  and a write past the page's block for an index off its end.
- Vulkan's three setters had no check at all; vk->overlay.vertex is
  NULL after vulkan_overlay_free().
  All now return for a NULL array or an index at or past the count.

d3d9 (cg, hlsl)
- load() set overlays_size whether or not the calloc() behind it had
  worked. It fails cleanly now; a page with no images is no page.
- set_alpha, vertex_geom, tex_geom: bounded, and NULL-checked.
- hlsl ignored LockRect()'s result and copied to whatever d3dlr held;
  cg already checked it.
- enable() set its flag inside a loop over the page's images, so never
  for a page with none. Set once.
- full_screen(): NULL driver or array is a return.

d3d10
- d3d10_free_overlays() released each texture and never freed the
  array holding them, while every load() made a new one over it: a
  leak per page switch and one at teardown. Freed; count and buffer
  pointer cleared with it.
- load() called through an unchecked calloc(), CreateBuffer() and
  Map(). A page that cannot be built is freed and reported.
- The setters mapped a buffer that might not exist and indexed it
  without a bound. One helper refuses a NULL buffer, an index at or
  past the count, and a Map() that failed.

d3d11
- The sprite buffer is "reused across pages while it is big enough" -
  but d3d11_free_overlays(), run at the top of every page load, had
  already released it, so every page switch was a CreateBuffer. The
  page and the buffer now go separately: the page at a page load, the
  buffer at teardown.
- A page whose sprite buffer could not be made or mapped kept its
  count; it is freed and reported.
- The setters ignored Map()'s result and wrote through pData, which a
  failed Map() leaves uninitialised, with no bound. Same helper as
  d3d10's.

d3d12
- A load_textures page copied each of the pack's d3d12_texture_t. The
  deferred upload's state (dirty, upload_fence, the upload buffer)
  lives in that struct, so copy and original part company at the
  first draw: the pack's struct stays dirty for good and every later
  page re-records an upload of every image it shares; and an animated
  image's next frame marks the pack's struct dirty while the driver
  draws from a copy that is not, so the animation stands still. The
  page now holds pointers (overlays.refs): into its own array for a
  load() page, to the pack's structs for a load_textures page.
- load_textures released the previous page's own textures and the
  sprite buffer without draining the queue. The comment's reason - the
  queue is drained at the top of every frame, so nothing reads them
  between frames - has it backwards: the last frame may still be in
  flight. The page free drains when, and only when, it has textures
  of its own to release.
- The sprite buffer was released at the top of every page load, so
  its reuse had nothing to reuse and every page switch was a drain
  and a new buffer. Kept across pages; released, behind a drain, at
  teardown.
- A page that cannot be built is freed and reported; a zero handle is
  refused rather than dereferenced; the setters go through the same
  kind of helper as d3d10's and d3d11's.

metal
- loadImages: stored -newTexture:'s result straight into an
  NSMutableArray; nil there is an exception. The page ends at that
  image and reports failure.
- -_getForIndex: answered &contents[index * 4] whatever the index and
  whether or not there was a buffer. NULL for both now, and the three
  updates return on NULL.
- Ownership needed nothing: the array retains the pack's textures.

CI
- tools/overlay_setter_bounds.py, beside the ownership check in CI
  Linux libretro-common tests: for each audited driver, set_alpha,
  vertex_geom and tex_geom - or the one helper they map through - must
  compare the index with the page's count before writing; for Metal,
  -_getForIndex: must check the buffer's length and its callers take
  NULL. Against the drivers as they were: 25 failures across all ten.
- overlay_textures_test asks load_page for its exact answer and gains
  a lane where the driver's load() fails.

Tested: both CI scripts; mingw gnu99 + linux-c89 syntax gate on all
eleven C files; gl1, gl2, gl3, vulkan and the frontend compiled in a
configured Linux build; overlay_textures_test plain and THREADS=1
under ASan + UBSan with leak detection. Not tested: metal.m cannot be
compiled here, and none of the D3D or Metal code has been run - no
runner and no machine here has those drivers.
2026-09-19 12:08:48 +02:00
LibretroAdmin d588459f53 CI: no video driver deletes overlay textures it was lent
No runner can run a GL or D3D driver, so the gl2 overlay blackout got
through with four correct siblings beside it. tools/
overlay_texture_ownership.py is the check at source level: every file
in gfx/drivers that implements load_textures must set its "borrowed"
marker when it takes the pack's handles, test it (negated) where it
frees a page, and clear it so a later load() page is not leaked.
metal.m is exempt, with the reason in the script: its textures are ARC
references. vulkan.c clears its marker by wiping the overlay struct,
which the script is told about.

Against gl2.c as it was, it fails with "the marker is never tested"
and "never cleared"; d3d11, d3d12, gl3 and vulkan pass. Runs beside
the VFS backend parity check in CI Linux libretro-common tests.
2026-09-19 11:52:50 +02:00
LibretroAdmin 78fda8c573 gl2: a page switch does not delete the overlay pack's textures
On the GL driver, rotating an Android device turned the on-screen
overlay into semi-transparent black boxes that stayed until the core
was restarted (reported by parkerlreed, Galaxy Fold, Adreno 840,
GLES 2 context, threaded video off).

Since df4b5a7e0b a page of the overlay is handed to the driver as the
pack's own texture names (load_textures). gl2_overlay_alloc() marks
such a page GL2_FLAG_OVERLAY_BORROWED - and nothing ever read the
flag. gl2_free_overlay() runs at the top of every page load and called
glDeleteTextures() on whatever names the previous page held, so the
first page switch deleted the pack's textures. A rotation is a page
switch (landscape to portrait and back), most names are on both pages,
and the pack - still cached, still sure it has its textures - went on
handing out dead names: an unbound texture samples as black, the
overlay's alpha does the rest. gl3, d3d11, d3d12 and vulkan honour
their marker; gl2 alone did not.

gl2_free_overlay() now deletes only names the driver made itself
(load()), and clears the flag so the next such page is not leaked.
2026-09-19 11:52:46 +02:00
LibretroAdmin f7393f3cc6 verbosity: flush every log line again
4552975f37 stopped flushing INFO/DEBUG lines to the log file and only
flushed [ERROR] and [WARN], to save the per-line fflush cost. In
practice that costs more than it saves:

- When RetroArch crashes, the lines that explain the crash are
  usually the informational ones written just before it. Those were
  exactly the lines left sitting in stdio's buffer and lost, which
  makes crash reports much harder to read in the cases where gdb
  does not help.
- Anything following the log live (tail -f, monitoring scripts) saw
  nothing until the buffer happened to fill (#19571).

Go back to the previous behaviour: one write and one flush per line,
for every tag. rarch_log_tag_wants_flush() and the flush parameter on
rarch_log_line_emit() are removed. The strcmp tag matching for the
Android log priority from afd57d615e stays - that overread fix is
independent of the flush policy.

The log-atomicity harness's flush lane now asserts the promise that
matters: a RARCH_LOG (informational) line must be readable from the
file as soon as the call returns, before any deinit.

Fixes #19571
2026-09-19 11:43:44 +02:00
Davey Hughes f9c96e80d9 vulkan: pass a failed mailbox fence wait on as the acquire's result (#19582)
The emulated mailbox checked its fence wait only for VK_TIMEOUT, so
VK_ERROR_DEVICE_LOST or an out-of-memory error handed the main thread
the image as VK_SUCCESS. A lost device was still caught, by the next
frame's submit, but only after that frame had been recorded and
submitted against it.

Carry the wait's result instead. An error is handed over the same way
as an image, so the main thread's existing acquire error path takes it
from there: it drops the swapchain and, for a lost device, raises
VIDEO_FLAG_GPU_DEVICE_LOST.
2026-09-19 10:55:46 +02:00
LibretroAdmin 405c7aad2c CI: cheevos_login_test runs against a TLS 1.3 capable Mbed TLS
The runner's libmbedtls-dev is 2.28, which has no TLS 1.3, so the mbed
leg could not see the session-ticket read failure (#19580) or the
missing psa_crypto_init(). Add a leg that builds Mbed TLS 3.6.0 - the
one release that shows both - and runs the harness against it. Library
only, about 20 s; a tarball download that still fails after retries
skips the leg with a warning instead of failing the job.

3.6.0 also ignores MBEDTLS_SSL_VERIFY_OPTIONAL under TLS 1.3, so the
harness can no longer lean on it: mbed_seed.c wraps the real backend's
ssl_socket_init and adds the test CA, as bear_seed.c does for BearSSL.
The handshake now verifies on every Mbed TLS version.

Makefile: MBEDTLS_DIR=<built tree> selects the library, and clean
removes both seed objects whichever SSL_BACKEND it is run with - a
stale mbed_seed.o built against other headers links and then fails
every TLS case.

Verified on 3.6.0: without the psa_crypto_init() change 4 cases fail,
without the ticket change case 6 fails, with both all pass (ASan+UBSan).
2026-09-19 10:44:23 +02:00
LibretroAdmin 9e09ed3783 net_socket_ssl_mbed: call psa_crypto_init() on Mbed TLS 3.x
Mbed TLS 3.x runs TLS 1.3 on top of PSA and leaves psa_crypto_init()
to the application. The backend only called it for 4.x. 3.6 compiles
TLS 1.3 in by default, so with a system 3.6 the result was:

  3.6.0   every handshake fails with MBEDTLS_ERR_SSL_INTERNAL_ERROR
          (-0x6C00) while building the ClientHello key share - no
          HTTPS at all, RetroAchievements login included
  3.6.1+  the library quietly stops offering TLS 1.3 when PSA is not
          initialised, so connections fell back to TLS 1.2

Call it on 3.x whenever TLS 1.3 or MBEDTLS_USE_PSA_CRYPTO is compiled
in. The bundled 2.x and the 4.x path are unchanged.
2026-09-19 10:44:23 +02:00
Davey Hughes 1e1300cca4 net_socket_ssl_mbed: read past TLS 1.3 session tickets (#19580)
A TLS 1.3 server sends NewSessionTicket messages after the handshake.
Mbed TLS 3.6.0 and 4.x report each from mbedtls_ssl_read() with
MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET, asking to be called again;
3.6.1 and later 3.6 releases drop tickets by default. Neither receive
path knew the code. The nonblocking one only got past it when isagain()
happened to find EAGAIN left in errno, and otherwise failed the
transfer, so an HTTPS request to a TLS 1.3 server such as
retroachievements.org failed at random with receive_header_failed: the
cause of intermittent "Login failed: No response" with those versions
as the system Mbed TLS. Keep reading instead.

cheevos_login_test gains a case that reads with the tickets already
buffered and errno cleared, which fails without this change on Mbed TLS
3.6.0 or 4.x.
2026-09-19 10:40:24 +02:00
LibretroAdmin 8fda74c6b5 CI: late handles, promotion, and a pack freed mid-upload are pinned
overlay_textures_test, THREADS=1:

- late handles now holds the whole sequence: both pages through load()
  with live pixels while the uploads are pending, the uploads posted
  exactly once, the page on screen promoted to textures when the
  handles are in, the pixels gone after that and not before, a second
  promote doing nothing, and the next page switch uploading nothing;
- late and declined / late and failed: the handles come in and the
  driver declines them, or the thread could not make them - load()
  from then on, pixels kept, no texture held for nothing;
- freed in flight: the pack is freed, and separately the driver torn
  down, with the uploads still queued, and the video thread runs
  afterwards. Before gfx_surface_free_adopt() this was a
  heap-use-after-free on the video thread's read of the pixels; the
  torn-down pack that lives on reports nothing to come back from and
  is never handed to load().

Already in the CI step (ASan + UBSan with leak detection), so the
adopted pixels are checked for being freed as well as for not being
freed early.
2026-09-19 10:36:45 +02:00
LibretroAdmin 7d27f3a9d2 Overlays: threaded video gets its textures, and a pack freed mid-upload is safe
Two things the threaded lanes of overlay_textures_test turned up.

Under threaded video the pack's textures were never used.
input_overlay_upload_textures() posts the uploads and polls for the
handles in the same breath; the video thread has not woken yet, so the
handles are not there, and the function released everything and had
the page shown through load(). The uploads still ran, and their
textures were unloaded as they completed. The next page switch did it
all again: three page loads of a three-image pack measured nine
texture uploads thrown away and three load() calls, which is more work
than before the pack's textures existed.

Late handles are now a state, not a failure. The surfaces are kept,
the page goes through load() this once, and
input_overlay_promote_textures() - one call per input poll, a few
pointer tests when nothing is pending - hands the page on screen to
the driver as textures when the last handle is in, after which the
pixels go as they do unthreaded. A page switch in the meantime posts
nothing. A pack that will not be shown as textures (upload failed,
driver declined) keeps its pixels and gives back what was uploaded.

And a use-after-free, narrow but real. A still image's upload borrows
the pack's pixels (gfx_surface_submit_external). Freeing the pack, or
tearing the driver down, with that upload still queued left the video
thread to read freed memory: the surface knew to outlive its flight
(dying), the pixels did not. gfx_surface_free_adopt() lets such a
surface take the pixels with it and free them at the completion;
input_overlay_release_textures() uses it, and the image is left with
its size and no pixels, exactly as after the pixels are dropped.
input_overlay_has_source() now asks every image for its pixels, not
the first, since a release in flight takes only some.
2026-09-19 10:36:45 +02:00
LibretroAdmin e8817a997e CI: the overlay's textures under threaded video, both ways the race goes
overlay_textures_test was built without HAVE_THREADS, so what happens
under the threaded wrapper was read off the code and never run. With
THREADS=1 the sample now links a stub of the wrapper's async upload
path: video_thread_async_post() queues the node as the real one does,
video_thread_async_poll() delivers completions on the caller, and the
video thread is a function the test calls at the point in the sequence
where it wants that thread to have got round to its queue. The race
between the posts and the poll is therefore the test's to decide, and
both outcomes are lanes:

- prompt handles: the uploads finish before the poll. Both pages go as
  textures, each with its own images, and the pixels go afterwards.
  Against 86fbf34e01's code this lane fails exactly as the unthreaded
  one does - threaded video was never immune, only slow enough;
- late handles: the poll finds nothing, the page goes through load()
  with live pixels, the pack keeps them, and when the thread runs it
  reads pixels that are still there. Nothing outlives the pack.

Runs under ASan + UBSan in CI Linux samples/input beside the existing
two builds; well under a second.
2026-09-19 10:36:45 +02:00
Davey Hughes fffaa74e2f vulkan: keep waiting on the mailbox acquire fence instead of giving up (#19581)
vkAcquireNextImageKHR has already succeeded by the time the emulated
mailbox waits on the fence, so a VK_TIMEOUT there means only that the
presentation engine has not released the image yet. Treating it as a
failure left the thread idle without re-arming REQUEST_ACQUIRE, while
the main thread kept HAS_PENDING_REQUEST set, so nothing asked for
another acquire and video stayed frozen until fast-forward ended and
the mailbox was torn down.

Re-arming is not an option: the acquired image cannot be handed back,
and the next acquire would pass a fence that is still pending. Keep
waiting on the same fence, checking the DEAD flag between waits so
teardown stays prompt.

Drivers that let the fence rather than the acquire carry the wait reach
this. AMD's Windows driver holds the fence for tens of milliseconds
during a display stall, and minimizing a fast-forwarding exclusive
fullscreen window there froze video in 3 of 4 runs before this change
and in none after.

Leaving the wait on DEAD leaves the fence pending, and a pending fence
must not be destroyed, so wait on it once more first, bounded by the
same refresh-derived timeout as the mailbox's other waits so a driver
that never signals cannot hang the join.
2026-09-19 10:20:14 +02:00
LibretroAdmin 427de41229 CI: every route an overlay page takes to the driver is pinned
samples/input/overlay_textures compiles input/input_overlay_textures.c
as it is against the real gfx_surface and a stub driver that reads
every pixel it is handed, so a stale pointer is an ASan report rather
than a picture nobody looks at. The pack is two pages over three
images, one on both pages. Lanes:

- textures: a driver with load_textures gets each page's own images'
  textures and load() is never reached;
- page switch: the second page uploads nothing;
- pixels go: once the driver has the textures the pixels are
  released and the sizes stay;
- declined / no such path / upload fails: a driver that answers
  load_textures with false, has none, or cannot make a texture gets
  live pixels through load() on every page, and the pack stays whole;
- spent pack: textures gone with the driver and pixels gone before
  them - nothing is shown, and the pack says it cannot come back;
- no images: a pack of hitboxes alone is always reusable.

Against 86fbf34e01's code the first lane is a heap-use-after-free in
load(); with the pixels dropped before load_textures() has answered,
the declined lane fails. Runs plain and under ASan + UBSan in CI Linux
samples/input, well under a second each.
2026-09-19 10:10:16 +02:00
LibretroAdmin 75359a9e4a Overlays: whoever takes the loader's payload frees the animated list
dac760d996 added anim_list to the payload a finished overlay load hands
over; the loader forgets it at the hand-off, as it does the image
list. Two takers did not free it:

- overlay_budget_test's consumer, which is why the ASan lane of CI
  Linux samples/tasks reports 4752 bytes leaked in 12 allocations,
  one string_list per successful load
  (task_push_overlay_load_default, task_overlay.c:1500);
- input_overlay_loaded() when it cannot allocate the pack, which
  freed the image list and leaked this one with its file buffers.

Both now free the entries' buffers and then the list.
2026-09-19 10:10:12 +02:00
LibretroAdmin e9468dedf8 Overlays: the pixels go after the driver has the textures, not before
With threaded video off, enabling an input overlay showed one
oversized blob across the screen or took RetroArch down (reported by
sonninnos, bisected to 86fbf34e01).

86fbf34e01 freed the pack's decoded pixels inside
input_overlay_upload_textures(), as soon as every image had a handle.
The very next loop builds each page's handle list by matching the
page's pixel pointers against the unique images' - which had just been
set to NULL. No entry matched, the upload released everything and
returned false, and input_overlay_load_page() fell back to load() with
the pages' own copies of those pointers: freed memory, handed to the
driver as images. With threaded video the handles are not there at the
poll, the function returns before the free, and the page goes through
load() with live pixels, which is why only the unthreaded case showed.

There was a second way in. The wrapper's overlay table answers
load_textures for any driver, and the one beneath it may have no such
path (d3d9, d3d10, gl1, ...): load_textures() returns false after the
upload succeeded, and the fallback is load() again - so freeing at the
upload leaves that route nothing to read either.

The pixels are now released by input_overlay_load_page(), and only
once load_textures() has answered true. The pages' copies of the
pointers are cleared with them, so nothing is left that looks like an
image and is not one. The sizes are kept, as 86fbf34e01 meant them to
be: image_texture_free() zeroes them. A pack left with neither
textures nor pixels shows nothing instead of reaching load(), and the
cache check in input_overlay_init() becomes input_overlay_has_source(),
which also stops a pack with no images at all from being reloaded from
its path on every toggle.
2026-09-19 10:10:08 +02:00
LibretroAdmin d5e7337b48 Overlays: the pack's textures get a file of their own
The upload of a pack's images, the page lists built from them, their
release, and the choice between showing a page as textures
(load_textures) or as pixels (load) move from input_driver.c to
input/input_overlay_textures.c, unchanged. input_overlay_load_active()
keeps the per-page alpha and geometry and calls the new
input_overlay_load_page() for the rest.

Nothing behaves differently. input_driver.c cannot be compiled outside
the frontend, so none of this could be put under a test; the new file
needs gfx_surface and four driver entry points, which a sample can
stub. The next commits fix what 86fbf34e01 broke here and then pin it.
2026-09-19 10:10:04 +02:00
LibretroAdmin b4e5db3c56 cheevos widgets: no placeholder, and no gap, for a badge a few frames away
A badge that is on disk reaches a widget a few frames after it is
asked for - decoded by a task, uploaded by the video thread, never
waited on - and the progress tracker and the challenge indicators drew
the placeholder trophy for those frames. The tracker is shared by
every achievement with measured progress, so a game advancing two of
them at once has it change hands every few frames, and it threw the
old badge away and loaded the new one each time: a flicker without
end.

rcheevos_get_badge_texture_ex() says why a 0 is a 0: pending while the
file is on disk and loading, not pending while it is being downloaded
or after a failure.

Progress tracker:
- it keeps the last four badges it has shown, so going back to one is
  immediate and loads nothing
- an update whose badge is not there yet is not applied until the
  badge arrives: what is on screen - the previous progress, or nothing
  on a first appearance - stays exactly as it is meanwhile, text and
  badge together. The wait is at most CHEEVO_BADGE_HOLD_FRAMES frames,
  and none at all when the badge is not coming soon; then the
  placeholder is drawn, as before
- the hide timer does not fire under a waiting update
- its textures are unloaded on context destroy and on free, which
  used to leave the handle behind

Challenge indicators: one that has just asked for its badge is not
drawn while the answer is pending, under the same bound, and keeps its
place so its neighbours do not move. They now ask before they draw
instead of after, so the badge shows a frame sooner.

The holds count frames; no clock is read for them.
2026-09-19 10:01:53 +02:00
github-actions 1089625974 Fetch translations from Crowdin 2026-09-19 00:23:43 +00:00
libretroadmin 2f31b9e11a Overlays: an animated image needs one slot, not two
A surface with a submit in flight refuses every slot, not just the
one it is using, so the second slot an animated overlay image was
given could never be written: its frames are composed on the main
thread and submitted at once, which leaves no window for a slot to
fill while another is out. That is a frame of memory per animated
image - eight megabytes for a 1080p animated border - for something
unreachable. The slot count goes to one, and the branch that picked
between slots says what it always did: a frame arriving while the
last is still out is dropped, and the next poll tries again.

Two slots remain right where a worker thread decodes into one while
the other is with the video thread, which is the animated thumbnail's
case and not this one. The sample holds the contract and records the
reason.
2026-09-19 00:05:44 +02:00
libretroadmin dac760d996 Overlays: an animated PNG in the pack plays
An overlay image that carries an acTL animation chunk keeps its file
bytes and gets a stream over them; the pack gives it a surface with
slots rather than a static one, and its already-decoded first frame
goes into the first slot, so nothing decodes twice. Each poll composes
whatever frame is due into a free slot and submits it, which updates
the texture in place: the pages' handles stand, so a page switch is
still an upload of nothing and an animation costs no texture of its
own. A slot the video thread has not finished with means the frame is
skipped rather than waited for - an overlay is a control surface, and
a late button image is worse than a dropped one - and the end of a
pass rewinds, since a control surface that animates should keep doing
it. A hidden overlay does not advance: composing frames nobody can
see is work with no observer, and a resumed loop is invisible on
button art.

An animated image keeps its compressed bytes and its slots where a
still releases its pixels once the texture exists, which is what a
stream needs to decode from. samples/gfx/overlay_apng builds its own
APNG and holds the contract the pages depend on: frames that differ,
a pass that ends and replays, one texture across the submits, and a
free with a frame still in flight.
2026-09-18 22:42:40 +02:00
libretroadmin 247519d69c Overlays: a 16-bit PNG in the pack keeps its ten bits where the driver samples them
The still loader could never be asked for the extra precision, so a
16-bit PNG was flattened to eight bits a channel whatever the driver
could sample. texture_image::pix10 is an ask on the way in and an
answer on the way out now: set it and a decoder with a 10-bit path -
rpng for 16-bit PNG, the video stills - emits XRGB2101010 and reports
what it produced; leave it clear and the decode is the 8-bit one it
always was. A failed decode clears it, so nothing mistakes a stale
request for an answer.

The overlay loader asks when the surface layer says the driver
samples XRGB2101010, which is how a pack's artwork reaches an HDR
swapchain without a pass that throws its low bits away first. A
driver that later cannot sample it narrows the image as before.
2026-09-18 21:06:19 +02:00
libretroadmin 86fbf34e01 Overlays: the pack's pixels go once they are on the GPU
A loaded pack held its decoded pixels for the whole session against
one event: a video reinit, where the textures die with the driver.
Measured against libretro/common-overlays that is 0.9 MiB for a
gamepad pack and up to 40 MiB for a keyboard, a 4K border or a 6x
landscape pad - held the entire time to save a decode at a moment
that tears down and rebuilds a graphics driver anyway, and one that
reloads the overlay from its path regardless
(video_driver_init_internal). So the pixels are freed as soon as
every image of the pack has its texture, and the width and height
stay, which is what the layout reads.

A cached pack is reusable only while it still has something to
upload from, so the cache check asks for live textures or live
pixels and a pack that has neither is reloaded from its path; an
upload with nothing to upload declines, which the existing
TEXTURES_DECLINED fallback already handles.
2026-09-18 20:54:48 +02:00
libretroadmin 807d9f1ad3 gfx: count what a loaded overlay pack keeps in memory, and let the harness print its numbers
An overlay pack keeps its decoded pixels after the textures exist, in
case a video reinit has to upload them again. Whether that is worth
having is a question about how many megabytes it is, so the counters
answer it: overlay_pixel_kib rises with each image uploaded for a pack
and falls when the pack's textures are released, which also makes the
accounting checkable.

The Vulkan harness target filtered its log down to the pass and
failure lines, so the baseline lines the counters print - the loads,
updates, posts and copies a run actually made - never reached the job
meant to report them. It passes those through now, and the overlay
lane prints and checks the pack figure beside the rest.
2026-09-18 20:20:24 +02:00
libretroadmin 61f2f72964 gfx_display: the white texture says which channel order it is
The one-by-one white texture is built on the stack and handed to the
driver with its channel order never written, so the drivers read
whatever the stack held there. Four 0xff bytes come out the same
either way, which is why it has gone unnoticed, but the field is the
caller's to set and every other producer sets it: it comes from the
surface layer now, like the rest.
2026-09-18 18:22:09 +02:00
6902 changed files with 1053938 additions and 921289 deletions

No files matched your search

+391 -1
View File
@@ -26,16 +26,35 @@ 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: Get short SHA
@@ -48,3 +67,374 @@ 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 record/record_audio_tap.c record/drivers/record_mediacodec.c 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_pipe.c \
record/record_android_capture_pool.c record/record_android_capture_device.c record/record_android_capture_request.c record/record_android_capture_config.c record/record_android_capture_ops.c \
record/record_android_capture_egl.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_session_mediacodec.c \
record/record_dispatch_mediacodec.c record/record_gpu_dispatch_mediacodec.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 -Wframe-larger-than=2048 \
-DANDROID -DHAVE_VULKAN -DHAVE_RECORD_MEDIACODEC -DHAVE_CONFIGFILE \
-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_session_mediacodec_|record_dispatch_mediacodec_|record_gpu_dispatch_mediacodec_|record_mux_mediacodec_|record_audio_tap_|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 -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
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
jni-contract:
runs-on: ubuntu-latest
strategy:
matrix:
cc: [gcc, clang]
env:
APT_PKGS: libvulkan-dev mesa-vulkan-drivers ffmpeg
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 test 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 }}
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Native decoder session failure and Surface timestamp contracts
run: make -C samples/gfx/mediacodec_session clean all check CC=${{ matrix.cc }} SANITIZER=address,undefined
- name: Hardware startup, first-frame refusal and software playback recovery
run: |
for threads in 0 1; do
make -C libretro-common/samples/formats/video_player clean all CC=${{ matrix.cc }} THREADS="$threads" SANITIZER=address,undefined
libretro-common/samples/formats/video_player/video_player_test
done
- name: HEVC drain and software output after hardware admission refusal
run: |
make -C samples/gfx/d3d11_video_decode rh265_hw_stream_test hevc_color_stream_test CC=${{ matrix.cc }} SANITIZER=address,undefined
(cd samples/gfx/d3d11_video_decode && ./rh265_hw_stream_test && ./hevc_color_stream_test)
- name: Execute RGB shader crop and SDR PQ HLG pixel oracle on Vulkan software device
run: make -C samples/gfx/mediacodec_session gpu-check CC=${{ matrix.cc }} SANITIZER=
- name: HEVC Annex-B configuration and packet preservation
run: make -C samples/gfx/mediacodec_hevc_packet clean all check parser-check CC=${{ matrix.cc }} SANITIZER=address,undefined
ndk-compile:
runs-on: ubuntu-latest
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: Strict C89, SDK function signatures and Android file-offset ABIs
run: |
set -euo pipefail
bin="$MEDIA_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin"
sources=(mediacodec_inventory android_image_queue android_image_ndk android_frame mediacodec_session mediacodec_color mediacodec_frame_export mediacodec_hevc_packet mediacodec_hevc_decoder mediacodec_hevc_backend mediacodec_vp9_decoder mediacodec_vp9_backend mediacodec_av1_decoder mediacodec_av1_backend vulkan_android_image vulkan_android_sync vulkan_android_pipeline vulkan_android_submit vulkan_android_queue vulkan_android_cache_entry vulkan_android_cache vulkan_android_device)
unity="$RUNNER_TEMP/android-media-unity.c"
printf '#include "%s/gfx/common/mediacodec_hevc_registry.h"\n' "$PWD" > "$unity"
printf '#include "%s/gfx/common/mediacodec_vp9_registry.h"\n' "$PWD" >> "$unity"
printf '#include "%s/gfx/common/mediacodec_av1_registry.h"\n' "$PWD" >> "$unity"
printf '#ifndef MEDIACODEC_HEVC_REGISTRY\n#error registry atomics unavailable\n#endif\n' >> "$unity"
for source in "${sources[@]}"; do
printf '#include "%s/gfx/common/%s.c"\n' "$PWD" "$source" >> "$unity"
done
printf '#include "%s/record/record_mediacodec_encoder.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_mux_mediacodec.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_mediacodec_worker.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_mediacodec_surface.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_audio_tap.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/drivers/record_mediacodec.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_android_capture_images.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_android_capture_submit.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_android_capture_pipe.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_android_capture_config.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_android_capture_request.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_android_capture_device.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_android_capture_pool.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_android_capture_ops.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_android_capture_egl.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_android_capture_gl.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_android_capture_draw.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_mediacodec_gpu_worker.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_mediacodec_file.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_session_mediacodec.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_dispatch_mediacodec.c"\n' "$PWD" >> "$unity"
printf '#include "%s/record/record_gpu_dispatch_mediacodec.c"\n' "$PWD" >> "$unity"
for target in aarch64-linux-android armv7a-linux-androideabi; 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
for source in "${sources[@]}"; do
object="$RUNNER_TEMP/$source-$target-$api-$offset.o"
"$bin/$target$api-clang" \
-std=c89 -pedantic -Wall -Wextra -Werror -Wno-long-long -Wno-c99-extensions \
-DHAVE_THREADS -DHAVE_DYLIB -Wframe-larger-than=2048 -Ilibretro-common/include -Igfx/include \
"${extra[@]}" -c "gfx/common/$source.c" -o "$object"
if "$bin/llvm-nm" -u "$object" | grep -Eq ' U (AMedia|AImage|AHardwareBuffer|ANativeWindow_setFrameRateWithChangeStrategy)'; then
echo "Unexpected direct media API dependency: $object"
exit 1
fi
done
for record_source in record_audio_tap record_mediacodec_encoder record_mux_mediacodec record_mediacodec_worker record_mediacodec_surface record_android_capture_images record_android_capture_submit record_android_capture_pipe record_android_capture_pool record_android_capture_device record_android_capture_request record_android_capture_config record_android_capture_gl record_android_capture_draw record_mediacodec_gpu_worker record_mediacodec_file record_session_mediacodec record_dispatch_mediacodec record_gpu_dispatch_mediacodec record_gpu_queue record_audio_queue; do
"$bin/$target$api-clang" \
-std=c89 -pedantic -Wall -Wextra -Werror -Wno-long-long -Wno-c99-extensions \
-DHAVE_THREADS -DHAVE_DYLIB -Wframe-larger-than=2048 -Ilibretro-common/include -Igfx/include \
"${extra[@]}" -c "record/$record_source.c" \
-o "$RUNNER_TEMP/$record_source-$target-$api-$offset.o"
done
"$bin/$target$api-clang" \
-std=c89 -pedantic -Wall -Wextra -Werror -Wno-long-long -Wno-c99-extensions \
-DANDROID -DHAVE_VULKAN -DHAVE_RECORD_MEDIACODEC -DHAVE_CONFIGFILE \
-DHAVE_THREADS -DHAVE_DYLIB -Wframe-larger-than=2048 -Ilibretro-common/include -Igfx/include \
"${extra[@]}" -c "$unity" -o "$RUNNER_TEMP/unity-$target-$api-$offset.o"
"$bin/$target$api-clang" \
-std=c89 -pedantic -Wall -Wextra -Werror -Wno-long-long -Wno-c99-extensions \
-DANDROID -DHAVE_VULKAN -DHAVE_RECORD_MEDIACODEC -DHAVE_CONFIGFILE \
-DHAVE_WEBMPLAYER -DHAVE_THREADS -DHAVE_DYLIB -Wframe-larger-than=2048 \
-Ilibretro-common/include -Igfx/include "${extra[@]}" \
-c record/drivers/record_mediacodec.c \
-o "$RUNNER_TEMP/record-history-$target-$api-$offset.o"
for root_source in gfx/common/vulkan_common.c gfx/drivers/vulkan.c gfx/gfx_surface_player.c libretro-common/jni/rjni.c; do
"$bin/$target$api-clang" \
-DANDROID -DHAVE_MENU -DHAVE_GFX_WIDGETS \
-DHAVE_VULKAN -DHAVE_RMP4 -DHAVE_RVP9 -DHAVE_RAV1 -DHAVE_DYLIB -DHAVE_THREADS \
-Dinline=__inline__ -O1 -std=c89 \
-Werror=declaration-after-statement -Werror=frame-larger-than -Wframe-larger-than=4096 \
-I. -Ilibretro-common/include -Igfx/include "${extra[@]}" \
-c "$root_source" -o "$RUNNER_TEMP/$(basename "$root_source")-$target-$api-$offset.o"
done
for variant in vp9-only av1-only no-codecs recording-only; do
codec_flags=()
case "$variant" in
vp9-only) codec_flags=(-DHAVE_RVP9 -DHAVE_THREADS) ;;
av1-only) codec_flags=(-DHAVE_RAV1 -DHAVE_THREADS) ;;
no-codecs) codec_flags=(-DHAVE_THREADS) ;;
recording-only) codec_flags=(-DHAVE_THREADS -DHAVE_CONFIGFILE -DHAVE_RECORD_MEDIACODEC) ;;
esac
for root_source in gfx/common/vulkan_common.c gfx/drivers/vulkan.c; do
"$bin/$target$api-clang" \
-DANDROID -DHAVE_MENU -DHAVE_GFX_WIDGETS \
-DHAVE_VULKAN -DHAVE_DYLIB "${codec_flags[@]}" \
-Dinline=__inline__ -O1 -std=c89 \
-Werror=declaration-after-statement -Werror=frame-larger-than -Wframe-larger-than=4096 \
-I. -Ilibretro-common/include -Igfx/include "${extra[@]}" \
-c "$root_source" -o "$RUNNER_TEMP/$variant-$(basename "$root_source")-$target-$api-$offset.o"
done
done
for player_source in libretro-common/formats/video_player.c libretro-common/formats/image_transfer.c libretro-common/formats/webm/rwebm_video.c libretro-common/formats/mp4/rmp4_video.c; do
"$bin/$target$api-clang" \
-DHAVE_RMP4 -DHAVE_RWEBM -DHAVE_RVP9 -DHAVE_RAV1 -DHAVE_THREADS \
-Dinline=__inline__ -O1 -std=c89 -Wno-long-long -Wno-c99-extensions \
-Werror=declaration-after-statement -Werror=frame-larger-than -Wframe-larger-than=2048 \
-I. -Ilibretro-common/include "${extra[@]}" \
-c "$player_source" -o "$RUNNER_TEMP/$(basename "$player_source")-$target-$api-$offset.o"
done
for parser_source in libretro-common/formats/h265/rh265.c libretro-common/formats/vp9/rvp9.c libretro-common/formats/av1/rav1_hw.c libretro-common/queues/retro_spsc.c; do
"$bin/$target$api-clang" \
-DHAVE_THREADS -O2 -std=c89 -pedantic -Wall -Wextra -Werror \
-Wno-unused-parameter -Wno-long-long -Wno-c99-extensions -Wframe-larger-than=2048 -fstack-usage \
-Ilibretro-common/include "${extra[@]}" -c "$parser_source" \
-o "$RUNNER_TEMP/$(basename "$parser_source")-$target-$api-$offset.o"
done
"$bin/$target$api-clang" \
-O2 -std=c89 -pedantic -Wall -Wextra -Werror \
-Wno-unused-parameter -Wno-long-long -Wno-c99-extensions -Wframe-larger-than=2048 -fstack-usage \
-Ilibretro-common/include "${extra[@]}" -c libretro-common/formats/vp9/rvp9.c \
-o "$RUNNER_TEMP/threadless-rvp9.c-$target-$api-$offset.o"
# Link AV1-only roots with exactly their selected native sources.
av1_objects=()
for source in mediacodec_inventory android_image_queue android_image_ndk android_frame mediacodec_session mediacodec_color mediacodec_frame_export mediacodec_av1_decoder mediacodec_av1_backend vulkan_android_image vulkan_android_sync vulkan_android_pipeline vulkan_android_submit vulkan_android_queue vulkan_android_cache_entry vulkan_android_cache vulkan_android_device; do
av1_objects+=("$RUNNER_TEMP/$source-$target-$api-$offset.o")
done
"$bin/ld.lld" -r "${av1_objects[@]}" \
"$RUNNER_TEMP/av1-only-vulkan_common.c-$target-$api-$offset.o" \
"$RUNNER_TEMP/av1-only-vulkan.c-$target-$api-$offset.o" \
"$RUNNER_TEMP/rjni.c-$target-$api-$offset.o" \
"$RUNNER_TEMP/rav1_hw.c-$target-$api-$offset.o" \
-o "$RUNNER_TEMP/av1-only-root-$target-$api-$offset.o"
if "$bin/llvm-nm" -u "$RUNNER_TEMP/av1-only-root-$target-$api-$offset.o" | \
grep -Eq ' U (rav1_|rvp9_|rh265_|rjni_env$|mediacodec_|android_frame_|android_image_|vulkan_android_)'; then
echo "Unresolved private helper in AV1-only Android root"
exit 1
fi
for variant in no-codecs; do
if "$bin/llvm-nm" -u "$RUNNER_TEMP/$variant-vulkan.c-$target-$api-$offset.o" | \
grep -Eq ' U (mediacodec_|rav1_(set|unset)_hw_backend)'; then
echo "Unexpected native decoder dependency: $variant"
exit 1
fi
done
"$bin/ld.lld" -r \
"$RUNNER_TEMP/unity-$target-$api-$offset.o" \
"$RUNNER_TEMP/vulkan_common.c-$target-$api-$offset.o" \
"$RUNNER_TEMP/vulkan.c-$target-$api-$offset.o" \
"$RUNNER_TEMP/gfx_surface_player.c-$target-$api-$offset.o" \
"$RUNNER_TEMP/rjni.c-$target-$api-$offset.o" \
"$RUNNER_TEMP/video_player.c-$target-$api-$offset.o" \
"$RUNNER_TEMP/image_transfer.c-$target-$api-$offset.o" \
"$RUNNER_TEMP/rwebm_video.c-$target-$api-$offset.o" \
"$RUNNER_TEMP/rmp4_video.c-$target-$api-$offset.o" \
"$RUNNER_TEMP/rh265.c-$target-$api-$offset.o" \
"$RUNNER_TEMP/rvp9.c-$target-$api-$offset.o" \
"$RUNNER_TEMP/rav1_hw.c-$target-$api-$offset.o" \
"$RUNNER_TEMP/record_audio_queue-$target-$api-$offset.o" \
"$RUNNER_TEMP/record_gpu_queue-$target-$api-$offset.o" \
"$RUNNER_TEMP/retro_spsc.c-$target-$api-$offset.o" \
-o "$RUNNER_TEMP/media-root-$target-$api-$offset.o"
if "$bin/llvm-nm" -u "$RUNNER_TEMP/media-root-$target-$api-$offset.o" | \
grep -Eq ' U (record_android_capture_|record_mediacodec_|record_session_mediacodec_|record_dispatch_mediacodec_|record_gpu_dispatch_mediacodec_|record_mux_mediacodec_|record_audio_tap_|record_audio_queue_|record_gpu_queue_|retro_spsc_|rav1_|rvp9_|rjni_env$|mediacodec_|android_frame_|android_image_|vulkan_android_|video_player_(request_software|set_hw_fallback)|image_transfer_anim_stream_stop_hw|rwebm_video_stream_stop_hw)'; then
echo "Unresolved private Android media helper in root link"
exit 1
fi
done
done
done
+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'
+84
View File
@@ -0,0 +1,84 @@
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: 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
+27 -19
View File
@@ -18,28 +18,36 @@ concurrency:
jobs:
compile-matrix: # every codec combination real builds select, not just this one
runs-on: ubuntu-latest
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
timeout-minutes: 30
steps:
- name: Dependencies
timeout-minutes: 5
run: |
# The gl2 arms compile against the desktop GL and EGL headers,
# and the Dingux and RS90 video drivers against SDL 1.2's.
pkgs="build-essential libgl-dev libegl-dev libgles-dev libsdl1.2-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
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
+42 -4
View File
@@ -35,6 +35,9 @@ on:
- 'libretro-common/include/vfs/**'
- 'libretro-common/include/streams/file_stream.h'
- 'libretro-common/samples/file/vfs/**'
- 'libretro-common/fastjmp/**'
- 'libretro-common/include/fastjmp.h'
- 'libretro-common/samples/fastjmp/**'
- '.github/workflows/Cross-libc-vfs-samples.yml'
pull_request:
paths:
@@ -43,6 +46,9 @@ on:
- 'libretro-common/include/vfs/**'
- 'libretro-common/include/streams/file_stream.h'
- 'libretro-common/samples/file/vfs/**'
- 'libretro-common/fastjmp/**'
- 'libretro-common/include/fastjmp.h'
- 'libretro-common/samples/fastjmp/**'
- '.github/workflows/Cross-libc-vfs-samples.yml'
workflow_dispatch:
@@ -135,14 +141,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
@@ -169,3 +195,15 @@ jobs:
# Exercises the Win32 CreateFileMapping/MapViewOfFile
# backend, which no other job in the tree reaches.
./vfs_mapped_ptr_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
# samples job cannot see a fastjmp_buf that lost its alignment;
# this is the only lane that can. Under a second to build and run.
- name: fastjmp on Win64
working-directory: libretro-common/samples/fastjmp/fastjmp_test
run: |
set -euo pipefail
make clean
make
./fastjmp_test.exe
+5 -2
View File
@@ -26,6 +26,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 +36,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 +46,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
+43 -1
View File
@@ -28,10 +28,52 @@ jobs:
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
@@ -0,0 +1,51 @@
name: CI extern "C" prototype parity
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:
extern-c-prototype-check:
name: extern "C" definitions match their C prototypes
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
# Fixture pass: a matching definition is clean, a definition
# 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.
tools/check.sh tools/extern_c_prototype_check.c --selftest
- name: Tree check
shell: bash
run: |
set -eu
# A .cpp file that defines a win32_common.h function under
# extern "C" without including the header (uwp_main.cpp) is
# 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.
tools/check.sh tools/extern_c_prototype_check.c
+94
View File
@@ -0,0 +1,94 @@
name: CI FreeBSD
# Builds RetroArch inside a FreeBSD VM (GitHub has no FreeBSD runners)
# with the same feature set the games/retroarch port enables by default,
# minus Qt (very slow to install). This is what kept the port carrying
# patches on top of each release: libudev is FreeBSD's libudev-devd, the
# input headers come from dev/evdev/, and there is no linux_common.o to
# link against.
#
# Deliberately NOT installed: evdev-proto and v4l_compat (the Linux
# compatibility headers). A stock FreeBSD box must build udev + Wayland
# from the dev/evdev/ headers in base, and configure must turn V4L2 off
# on its own when libv4l is present but linux/videodev2.h is not.
on:
push:
branches: [master]
pull_request:
repository_dispatch:
types: [run_build]
permissions:
contents: read
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: 90
steps:
- 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:
release: "14.3"
usesh: true
copyback: false
prepare: |
pkg install -y \
gmake pkgconf \
ffmpeg freetype2 openal-soft mesa-libs \
libudev-devd libxkbcommon wayland wayland-protocols \
libX11 libXext libXxf86vm libXinerama libXrandr libXv \
sdl2 pipewire alsa-lib pulseaudio libdrm libv4l
run: |
set -e
freebsd-version
NCPU=$(sysctl -n hw.ncpu)
# --disable-pipewire must not leave HAVE_PIPEWIRE_STABLE
# behind just because libpipewire is installed.
./configure --disable-qt --disable-discord --disable-pipewire
grep -q '^HAVE_PIPEWIRE_STABLE = 0' config.mk
# libv4l2.pc is installed but linux/videodev2.h is not
# (v4l_compat): V4L2 must be auto-disabled, not left on to
# fail at compile time.
grep -q '^HAVE_V4L2 = 0' config.mk
# Real build: force the FreeBSD-specific paths on so a missing
# dependency fails configure instead of silently skipping them.
./configure --disable-qt --disable-discord \
--enable-udev --enable-x11 --enable-wayland --enable-sdl2 \
--enable-ffmpeg --enable-pipewire --enable-alsa \
--enable-pulse --enable-oss
gmake -j$NCPU clean
gmake -j$NCPU info all
./retroarch --version
# udev input must multiplex with kqueue here, so the binary
# must not need libepoll-shim's epoll_* at all.
if nm -u retroarch | grep -q 'epoll_'; then
echo "retroarch still references epoll (libepoll-shim)"; exit 1
fi
# A fresh RetroArch must pick up data sets the ports install
# under /usr/local/share/libretro (assets, info, shaders,
# autoconfig) without any retroarch.cfg edits.
sh samples/frontend/packaged_dirs/run.sh
# With v4l_compat the V4L2 camera and video processor core
# must still compile; only those two objects are rebuilt.
pkg install -y v4l_compat
./configure --disable-qt --disable-discord --enable-v4l2
grep -q '^HAVE_V4L2 = 1' config.mk
gmake -j$NCPU camera/drivers/video4linux2.o \
cores/libretro-video-processor/video_processor_v4l2.o
+45 -2
View File
@@ -22,13 +22,56 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-devkitpro:latest
image: devkitpro/devkitppc:latest
options: --user root
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 }}
+59 -17
View File
@@ -21,28 +21,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 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 ccache
timeout-minutes: 30
steps:
- name: Dependencies
timeout-minutes: 5
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
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 }}
+119 -27
View File
@@ -59,39 +59,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 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 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: 5
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
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
@@ -383,3 +400,78 @@ jobs:
| sort -u | head -20
fi
echo "[done] imageviewer headless smoke (soft-fail)"
- name: Screenshot on exit under LeakSanitizer (strict)
# The smoke above runs with detect_leaks=0, which is how a
# synchronous screenshot leaking its task state and the whole
# captured frame on every call went unnoticed: --max-frames-ss
# and save-state thumbnails both take that path. This run
# takes one and checks for leaks with only the system libraries
# suppressed - Mesa, X11, SDL, D-Bus and friends keep caches
# alive to exit that are not RetroArch's - so anything left is
# ours and fails the job.
env:
ASAN_OPTIONS: abort_on_error=1:detect_leaks=1:print_stacktrace=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
LSAN_OPTIONS: suppressions=/tmp/lsan-system.supp:print_suppressions=0
run: |
set -eu
cat > /tmp/lsan-system.supp <<EOF
leak:libGL
leak:libGLX
leak:libGLdispatch
leak:libgallium
leak:libLLVM
leak:_dri.so
leak:libEGL
leak:libgbm
leak:libX11
leak:libxcb
leak:libSDL2
leak:libdbus-1
leak:libfontconfig
leak:libfreetype
leak:libasound
leak:libpulse
EOF
mkdir -p /tmp/lsan-cfg /tmp/lsan-shots
cat > /tmp/lsan-cfg/retroarch.cfg <<EOF
video_driver = "sdl2"
audio_driver = "null"
video_threaded = "false"
video_fullscreen = "false"
video_windowed_fullscreen = "false"
screenshot_directory = "/tmp/lsan-shots"
EOF
set +e
timeout 120 xvfb-run -a -s "-screen 0 320x240x24" ./retroarch \
-c /tmp/lsan-cfg/retroarch.cfg \
-L cores/libretro-imageviewer/image_core.so \
/tmp/test.png \
--max-frames=60 \
--max-frames-ss \
--max-frames-ss-path=/tmp/lsan-shots/exit.png \
> /tmp/lsan.log 2>&1
rc=$?
set -e
if [ "$rc" != 0 ] || grep -qE "leak of|AddressSanitizer:|runtime error:" /tmp/lsan.log; then
echo "exit=${rc}"
grep -E -A 12 "leak of|AddressSanitizer:|runtime error:" /tmp/lsan.log | head -120
exit 1
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 }}
@@ -24,6 +24,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 +66,38 @@ 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: 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: 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 SANITIZER=thread
+86 -22
View File
@@ -21,32 +21,71 @@ concurrency:
jobs:
autoconf-tests:
name: Build and run the autoconfig regression suite
name: Build and run the autoconfig regression suite (${{ matrix.lane }})
runs-on: ubuntu-latest
timeout-minutes: 15
env:
APT_PKGS: build-essential zlib1g-dev libfreetype6-dev ccache
timeout-minutes: ${{ matrix.sanitizer == '' && 15 || 40 }}
strategy:
fail-fast: false
matrix:
# The suite fails on any sanitizer report in any run, so the
# sanitized lanes hold the whole frontend it drives - runloop,
# task queue, autodetect - to the same bar as the assertions.
include:
- lane: plain
sanitizer: ''
- lane: ASan + UBSan
sanitizer: address,undefined
- lane: TSan
sanitizer: thread
steps:
- name: Install dependencies
timeout-minutes: 5
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
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
@@ -59,11 +98,36 @@ jobs:
grep -q '^HAVE_TEST_DRIVERS = 1' config.mk
- name: Build
run: make -j"$(nproc)"
run: |
if [ -n "${{ matrix.sanitizer }}" ]; then
CFLAGS="-O0 -g1" CXXFLAGS="-O0 -g1" make -j"$(nproc)" SANITIZER=${{ matrix.sanitizer }} DEBUG=1
else
make -j"$(nproc)"
fi
- name: Run autoconfig regression suite
shell: bash
run: |
set -eu
test -x ./retroarch
./tests-other/test_autoconf.sh ./retroarch
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
else
./tests-other/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 }}
+73
View File
@@ -0,0 +1,73 @@
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
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
+39 -7
View File
@@ -23,20 +23,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
+87
View File
@@ -0,0 +1,87 @@
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
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 }}
@@ -0,0 +1,82 @@
name: CI Linux filter plugins
# Builds every video filter (gfx/video_filters) and audio DSP filter
# (libretro-common/audio/dsp_filters) plugin with its own Makefile and
# runs samples/gfx/filter_plugins over all of them: each plugin is
# loaded with RTLD_NOW into a host that does not link libm, so a
# symbol it cannot resolve fails the load, then fed a frame or a run
# of audio blocks at its default settings. Every video filter that
# 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.
on:
push:
branches:
- master
paths:
- 'gfx/video_filters/**'
- 'libretro-common/audio/dsp_filters/**'
- 'libretro-common/include/libretro_dspfilter.h'
- 'samples/gfx/filter_plugins/**'
- '.github/workflows/Linux-filter-plugins.yml'
pull_request:
paths:
- 'gfx/video_filters/**'
- 'libretro-common/audio/dsp_filters/**'
- 'libretro-common/include/libretro_dspfilter.h'
- 'samples/gfx/filter_plugins/**'
- '.github/workflows/Linux-filter-plugins.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:
filter-plugins:
name: Load and run every filter plugin
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- 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
make -C samples/gfx/filter_plugins
# The harness must not bring libm in on the plugins' behalf.
if readelf -d samples/gfx/filter_plugins/filter_plugins_test \
| grep -q 'libm\.so'; then
echo "filter_plugins_test links libm; it would hide a plugin that does not"
exit 1
fi
- name: Load and run every plugin
run: make -C samples/gfx/filter_plugins check
- name: Load and run every plugin under ASan, UBSan and LeakSanitizer
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
make -C samples/gfx/filter_plugins clean
make -C samples/gfx/filter_plugins check SANITIZER=address,undefined
- name: Load and run every plugin under ThreadSanitizer
env:
TSAN_OPTIONS: halt_on_error=1
run: |
set -eu
make -C samples/gfx/filter_plugins clean
make -C samples/gfx/filter_plugins check SANITIZER=thread
@@ -0,0 +1,84 @@
name: CI Linux frontend overhead [frame-path harness]
# What one runloop_iterate() costs when the core costs nothing, and
# whether anything on the frame path allocates. The harness links the
# shipping release objects with main() replaced, boots on the null
# drivers, runs a core that pushes one frame, one audio batch and one
# input read per retro_run, and counts heap calls over the frame path
# by glibc interposition - which is why this is a release build with
# no allocator sanitizer in the way. It also prints microseconds per
# frame under fast-forward and trips on a loose bound, so a blocking
# call or a sleep that lands on the frame path fails here rather than
# in a bug report about stutter.
#
# Everything a default desktop build compiles in is compiled in, so a
# product that starts costing the frame while idle is measured, not
# excluded.
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:
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 libfreetype6-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libavcodec-dev libsdl2-dev libxml2-dev yasm
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: Configure
# Non-Qt, as the harness requires (with Qt, main() lives in
# ui_qt.o). Everything else at its default.
run: ./configure --disable-qt
- name: Build (release)
run: make -j$(getconf _NPROCESSORS_ONLN)
- name: Run the frontend-overhead harness
shell: bash
run: |
set -o pipefail
SAN=none make -C samples/runloop/frontend_overhead check
make -C samples/runloop/frontend_overhead clean
+123
View File
@@ -0,0 +1,123 @@
name: CI Linux gl1 upload
# Runs the gl1 video driver under Xvfb with a test core and checks the
# screen against the frame the core drew, pixel for pixel, down each of
# the driver's upload paths: frames taken straight from the core's
# buffer, the no-GL_EXT_bgra swizzle, native RGB565, the GL 1.1 CPU
# expansion, and texture storage respecified across frame size changes.
on:
push:
branches:
- master
paths:
- 'gfx/drivers/gl1.c'
- 'libretro-common/gfx/scaler/pixconv.c'
- 'libretro-common/include/gfx/scaler/pixconv.h'
- 'samples/gfx/gl1_upload/**'
- '.github/workflows/Linux-gl1-upload.yml'
pull_request:
paths:
- 'gfx/drivers/gl1.c'
- 'libretro-common/gfx/scaler/pixconv.c'
- 'libretro-common/include/gfx/scaler/pixconv.h'
- 'samples/gfx/gl1_upload/**'
- '.github/workflows/Linux-gl1-upload.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:
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: 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, 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_OPENGL1 = 1' config.mk
make -j$(nproc)
ccache -s
- name: Run gl1_upload_test
shell: bash
run: |
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 }}
@@ -0,0 +1,196 @@
name: CI Linux library network backends
# The built-in TLS, SMB and NFS clients are the defaults and nothing is
# bundled. A system mbedTLS or BearSSL can stand in for the TLS client
# (--enable-mbedtls, --enable-bearssl) and a system libsmb2 for the SMB
# client (--enable-libsmb). Each lane configures with its option, builds
# RetroArch to the link, checks the binary carries that library's
# backend and not the built-in client, and then operates the backend:
# the TLS lanes run the cheevos login transport against a local HTTPS
# server, the libsmb2 lane runs the SMB VFS tests against a local smbd.
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:
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 libfreetype6-dev python3 openssl libmbedtls-dev libbearssl-dev samba ccache
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- lane: mbedtls
configure: --enable-mbedtls
present: mbedtls_ssl_handshake
absent: ' T ssl_socket_retro_was_resumed'
- lane: bearssl
configure: --enable-bearssl
present: br_ssl_client_init_full
absent: ' T ssl_socket_retro_was_resumed'
- lane: libsmb2
configure: --enable-libsmb
present: ' U smb2_connect_share'
absent: ' T rsmb_connect'
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: 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: |
set -eu
git clone -q --depth 1 https://github.com/sahlberg/libsmb2.git "$RUNNER_TEMP/libsmb2"
cmake -S "$RUNNER_TEMP/libsmb2" -B "$RUNNER_TEMP/libsmb2/build" \
-DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_SHARED_LIBS=ON >/dev/null
make -C "$RUNNER_TEMP/libsmb2/build" -j"$(nproc)" >/dev/null
sudo make -C "$RUNNER_TEMP/libsmb2/build" install >/dev/null
sudo ldconfig
echo "PKG_CONFIG_PATH=/usr/local/lib/pkgconfig" >> "$GITHUB_ENV"
pkg-config --modversion libsmb2 || PKG_CONFIG_PATH=/usr/local/lib/pkgconfig pkg-config --modversion libsmb2
- name: Configure
run: |
set -eu
./configure ${{ matrix.configure }} --disable-qt --disable-wayland --disable-ffmpeg
grep -E '^HAVE_(RETROSSL|MBEDTLS|BEARSSL|RETROSMB|SMBCLIENT|SSL) ' config.mk
case "${{ matrix.lane }}" in
mbedtls) grep -q '^HAVE_MBEDTLS = 1' config.mk; grep -q '^HAVE_RETROSSL = 0' config.mk ;;
bearssl) grep -q '^HAVE_BEARSSL = 1' config.mk; grep -q '^HAVE_RETROSSL = 0' config.mk ;;
libsmb2) grep -q '^HAVE_SMBCLIENT = 1' config.mk; grep -q '^HAVE_RETROSMB = 0' config.mk ;;
esac
- name: Build
run: make -j"$(nproc)"
- name: Check which backend linked
run: |
set -eu
nm retroarch | grep -q -- '${{ matrix.present }}'
if nm retroarch | grep -q -- '${{ matrix.absent }}'; then
echo "the built-in client is still linked in the ${{ matrix.lane }} build"; exit 1
fi
echo "[pass] ${{ matrix.lane }} backend linked, built-in client absent"
- name: Operate the TLS backend (cheevos login transport, ASan + UBSan)
if: matrix.lane != 'libsmb2'
working-directory: samples/network/cheevos_login
run: |
set -eu
case "${{ matrix.lane }}" in mbedtls) be=mbed ;; bearssl) be=bear ;; esac
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'
run: |
set -eu
sudo -E env PATH="$PATH" PKG_CONFIG_PATH="$PKG_CONFIG_PATH" WITH_LIBSMB2=1 \
samples/network/smb_retro/local_samba_test.sh
sudo make -C samples/network/smb_retro clean
# libsmb2 keeps process-wide state (its list of live contexts) with
# no lock of its own; the VFS backend must serialise it. A race
# there needs two cores to show up by timing, so the same tests run
# again under TSan against a TSan-built libsmb2, which reports it on
# any runner, every time.
- name: Operate the libsmb2 backend under TSan (instrumented libsmb2)
if: matrix.lane == 'libsmb2'
run: |
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 -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
sudo -E env PATH="$PATH" \
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 -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
+283 -92
View File
@@ -29,45 +29,43 @@ jobs:
libretro-common-tests:
name: Build and run libretro-common/Makefile.test
runs-on: ubuntu-latest
timeout-minutes: 15
env:
APT_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 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: 8
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"
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
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 }}
@@ -77,11 +75,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
@@ -107,6 +104,19 @@ jobs:
LIBCHECK_CFLAGS="$(pkg-config --cflags check)" \
CFLAGS="$(pkg-config --cflags check)"
echo "[pass] libretro-common unit test suites"
# The ARMv8 AES + PMULL path executes only on ARM; this
# runs the crypto suite under qemu-aarch64 in both the
# dispatched and the baseline-crypto shape.
make -f Makefile.test crypto-aarch64-check
make -f Makefile.test crypto-arm-check
make -f Makefile.test crypto-mips-check
# 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
@@ -121,93 +131,108 @@ 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 companion backend feature parity
- name: Check every command describes itself
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
# 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.
tools/check.sh tools/command_table_check.c
- name: Companion thumbnail engine regression test (plain, TSan, ASan)
- name: Check video and input code stay out of each other's state
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
# 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: Companion core regression test (plain, ASan)
- name: Check input drivers ask by name and do not take the frontend's state
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
# 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: C89 / Win32 syntax gate on the companion sources
- name: Check the GL drivers take the hardware ring's fence ahead of the swap
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
# 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: Cocoa companion regression harness (GNUstep AppKit, headless)
- name: Check the GL drivers set their vsync report with the swap interval
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
# 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: Win32 companion regression harness (mingw + Wine, headless)
- name: Check a setting's label exists wherever its translations name it
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
# 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
run: |
set -eu
# load_textures lends a driver the overlay pack's handles for
# one page; they are on the pack's other pages too and are
# the pack's to delete. gl2 marked them as borrowed and then
# deleted them anyway at the next page, so rotating an
# Android device blacked the overlay out until the core was
# 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.
tools/check.sh tools/overlay_texture_ownership.c
- name: Check overlay setters refuse an image the driver does not hold
shell: bash
run: |
set -eu
# set_alpha, vertex_geom and tex_geom are called whenever the
# layout, opacity or page changes, not only after a load()
# that worked: with no page the driver's arrays are NULL, and
# after a smaller page or a failed load the index is off the
# end. 7661ab06e7 bounded the GL geometry setters and missed
# 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.
tools/check.sh tools/overlay_setter_bounds.c
- name: WDM-KS ABI against the system ks.h
shell: bash
@@ -248,3 +273,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 }}
+43 -21
View File
@@ -23,32 +23,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: 5
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
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 }}
@@ -58,11 +67,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
@@ -193,3 +201,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 }}
+72
View File
@@ -0,0 +1,72 @@
name: CI Linux MCP over stdio
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:
mcp-stdio:
name: retroarch --mcp-stdio driving a core
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential pkg-config zlib1g-dev libfreetype6-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
- 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
+321 -22
View File
@@ -22,6 +22,23 @@ 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'
- '.github/workflows/Linux-menu-harness.yml'
pull_request:
paths:
@@ -30,6 +47,23 @@ 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'
- '.github/workflows/Linux-menu-harness.yml'
workflow_dispatch:
@@ -41,32 +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: 5
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
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 }}
@@ -76,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
@@ -98,10 +140,12 @@ jobs:
#
# Qt in particular has to go: with it enabled main() lives
# in ui_qt.o, and the harness replaces main().
# Networking stays on: the network settings harness below
# needs the SMB and NFS client screens compiled in.
./configure --disable-qt --disable-x11 \
--disable-opengl --disable-opengl_core \
--disable-opengl1 --disable-vulkan \
--disable-cheevos --disable-networking \
--disable-cheevos \
--disable-ffmpeg
make -j$(nproc)
@@ -124,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: |
@@ -146,6 +207,132 @@ jobs:
./samples/menu/playlist_nav/menu_playlist_nav_test
make -C samples/menu/playlist_nav clean
- name: Build and run menu_nav_repeat_stall_test
shell: bash
run: |
set -eu
# One press on a menu entry whose frame stalls - Ozone and
# XMB build a large playlist synchronously when the selection
# lands on it - has to move the selection once. The menu acts
# on input one poll old, so the hold time has to be measured
# between polls or the stall fires an auto-repeat. Drives the
# real runloop with a scripted input driver; also checks that
# a hold across the stall still repeats and that a steady hold
# waits out the initial scroll delay. Shares the non-Qt build
# made above.
samples/menu/nav_repeat_stall/build.sh
./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: |
set -eu
# The NFS Network Settings entry opened an empty "null"
# browser: the label its deferred list is pushed with had no
# row in msg_hash_lbl.h, so it resolved to "null" and the
# push found nothing. The harness resolves the SMB and NFS
# labels, opens Settings > Network and both screens through
# the real dispatch, and checks the Quick Menu no longer
# carries the two entries.
samples/menu/network_settings/build.sh
./samples/menu/network_settings/menu_network_settings_test
make -C samples/menu/network_settings clean
- name: Build and run resolution_reset_test
shell: bash
run: |
set -eu
# Start on the Resolution entry reinits and sets the video
# mode at the output size; it must keep the window state the
# frontend is in rather than take a windowed desktop session
# full screen. Shares the non-Qt build made above.
samples/menu/resolution_reset/build.sh
./samples/menu/resolution_reset/resolution_reset_test
make -C samples/menu/resolution_reset clean
- name: Build and run packed_rows_test
shell: bash
run: |
set -eu
# Settings rows that each own one half of a packed settings_t
# word - the custom viewport's origin and size, the window
# size pair - driven through their display strings, a right
# press and the dropdown list. Each row has to show and set
# its own axis and leave its partner as it stands. Shares the
# non-Qt build made above.
samples/settings/packed_rows/build.sh
./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: |
@@ -157,6 +344,23 @@ jobs:
# this job already built.
.github/workflows/scripts/hot-function-budget.sh
- name: Lock budget
shell: bash
run: |
set -eu
# Lock acquisitions on the paths meant to take none, or a
# counted few, against tools/lock_acquire_budget.ref: a lock
# put back on a per-frame path passes every lane and shows up
# only as a slower frame. Same release objects as above; this
# 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: |
@@ -174,3 +378,98 @@ jobs:
samples/gfx/threaded_video/build.sh
./samples/gfx/threaded_video/threaded_video_test 3
make -C samples/gfx/threaded_video clean
- name: Build and run content_load_test
shell: bash
run: |
set -eu
# The content load as a chain of main-thread tasks, against the
# real runloop on the same non-Qt menu build: frames presented
# between stages, one load at a time, a core's reinit deferred
# to the load's driver stage, the dummy-core fallback, a
# hardware-render request carried across the driver rebuild,
# the task queue surviving the load, and the close waiting out
# a save state from the frame loop.
samples/tasks/content_load/build.sh
./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.
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: |
set -eu
# The Explore index built in steps: one item per step hashes
# the same as the build in one go, and the explore task returns
# unfinished across checks with Threaded Tasks off.
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 }}
+130
View File
@@ -0,0 +1,130 @@
name: CI fullscreen media menu
on:
push:
branches: [master]
pull_request:
branches: [master]
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 --enable-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 samples/menu/rgui_media/fixture.mp4
- name: Real MaterialUI file-browser playback controls
run: python3 samples/menu/rgui_media/run.py --c89 --driver materialui samples/menu/rgui_media/fixture.mp4
- name: Real XMB file-browser playback controls
run: python3 samples/menu/rgui_media/run.py --c89 --driver xmb samples/menu/rgui_media/fixture.mp4
- name: Real Ozone file-browser playback controls
run: python3 samples/menu/rgui_media/run.py --c89 --driver ozone samples/menu/rgui_media/fixture.mp4
- 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: Natural MP4 folder playback and file controls across all menus
run: |
for driver in rgui materialui xmb ozone; do
python3 samples/menu/rgui_media/run.py --c89 --continuous --fullscreen-settings --require-cadence --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
done
- name: Desktop refresh source lifecycle across all menus
run: |
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
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
- name: Natural WebM folder playback across all menus
run: |
for driver in rgui materialui xmb ozone; do
python3 samples/menu/rgui_media/run.py --c89 --continuous --fullscreen-settings --require-cadence --driver "$driver" samples/menu/rgui_media/fixture-vorbis.webm
python3 samples/menu/rgui_media/run.py --c89 --continuous-error --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 --enable-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
- 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
+144
View File
@@ -0,0 +1,144 @@
name: CI Visual C++ 6 project (stand-in compiler)
on:
push:
branches:
- master
pull_request:
branches:
- master
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: 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
+282
View File
@@ -0,0 +1,282 @@
name: CI Linux network clients
# The built-in TLS, SMB2/3 and NFSv3 clients against real servers:
# openssl s_server, Samba, unfs3, nfs-ganesha and a MIT KDC on the runner, natively, as Windows
# builds under Wine, under ASan + UBSan + LSan, plus libFuzzer over the
# parsers. Its own job so the libretro-common unit-test job keeps its
# short budget.
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:
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: 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 }}
run: |
set -eu
# net_socket_ssl_retro.c end to end: every cipher suite, both
# curves, RSA / P-256 / P-384 certificates under a CA made on
# the spot, verify required / optional / disabled, and the
# refusals that must happen (wrong hostname, untrusted CA). An
# IP address matches only an iPAddress SAN entry, never the
# same address written as a DNS name.
samples/network/tls_retro/local_server_test.sh
# The same tool cross-linked for Windows: the sample's link
# line has to carry Winsock, which nothing on Linux checks.
make -C samples/network/tls_retro clean
- name: Built-in SMB2/3 client against a local smbd
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# net_smb2.c end to end against Samba on the loopback: every
# dialect 2.0.2 to 3.1.1 with signing mandatory, AES-CCM and
# AES-GCM sealing required, and a refused logon. Root for
# port 445; the script itself skips without smbd.
sudo samples/network/smb_retro/local_samba_test.sh
# and with Kerberos against a Samba AD DC provisioned on the
# spot, NTLM disabled on it so only a ticket can log in
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: 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
working-directory: ${{ github.workspace }}
run: |
set -eu
# The MinGW builds run, not just link: the crypto and keychain
# suites, then the three server matrices with the Windows tools
# talking to the same local openssl, Samba and unfs3.
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
make -C samples/network/tls_retro clean
make -C samples/network/smb_retro clean
# smbd needs root for port 445, and Wine refuses both a prefix
# 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 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
make -C samples/network/nfs_retro clean
samples/network/nfs_retro/local_unfs3_test.sh
make -C samples/network/nfs_retro clean
sudo -E env HOME=/root WINEPREFIX=/tmp/wineprefix-root samples/network/nfs_retro/local_ganesha_test.sh
sudo make -C samples/network/nfs_retro clean
sudo -E env HOME=/root WINEPREFIX=/tmp/wineprefix-root samples/network/nfs_retro/local_read_plus_test.sh
sudo make -C samples/network/nfs_retro clean
make -C samples/network/krb5_retro clean
samples/network/krb5_retro/local_kdc_test.sh
make -C samples/network/krb5_retro clean
- name: Fuzz the network parsers
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# libFuzzer over the X.509 parser and the TLS, SMB and NFS
# clients' receive paths (a listener in the harness answers the
# client with the fuzzer's input), under ASan + UBSan, from a
# seed corpus recorded off real servers. Any crash, timeout or
# leak artifact fails the step.
make -C libretro-common/test/fuzz fuzz-run FUZZ_SECONDS=30
if ls libretro-common/test/fuzz/crash-* libretro-common/test/fuzz/leak-* libretro-common/test/fuzz/timeout-* >/dev/null 2>&1; then
echo "fuzzer artifacts found"; exit 1
fi
make -C libretro-common/test/fuzz clean
- name: Network client matrices under ASan + UBSan + LSan
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# The three server matrices again, with the clients built under
# the sanitizers: every code path a real server drives, checked
# 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 -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
make -C samples/network/tls_retro clean
samples/network/tls_retro/local_server_test.sh
make -C samples/network/tls_retro clean
make -C samples/network/smb_retro clean
sudo -E samples/network/smb_retro/local_samba_test.sh
sudo -E samples/network/smb_retro/local_ad_dc_test.sh
make -C samples/network/smb_retro clean
make -C samples/network/nfs_retro clean
samples/network/nfs_retro/local_unfs3_test.sh
make -C samples/network/nfs_retro clean
sudo -E samples/network/nfs_retro/local_ganesha_test.sh
sudo make -C samples/network/nfs_retro clean
sudo -E samples/network/nfs_retro/local_read_plus_test.sh
sudo make -C samples/network/nfs_retro clean
make -C samples/network/krb5_retro clean
samples/network/krb5_retro/local_kdc_test.sh
make -C samples/network/krb5_retro clean
- name: Built-in NFSv3 client against unfs3
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# net_nfs3.c end to end against a user-space NFSv3 server built
# from unfs3's repository (Ubuntu no longer packages it), on
# fixed ports without a portmapper: mount, write, read, seek,
# stat, mkdir, rename, truncate, list, unlink, refused export.
samples/network/nfs_retro/local_unfs3_test.sh
# NFSv4.0 (one COMPOUND per operation, no portmapper or MOUNT,
# pseudo-filesystem export) against nfs-ganesha from apt
sudo -E samples/network/nfs_retro/local_ganesha_test.sh
# NFSv4.2 READ_PLUS against a fake server reporting the holes
# the filesystem really holds, its replies checked by
# Wireshark's own dissector
sudo make -C samples/network/nfs_retro clean
sudo -E samples/network/nfs_retro/local_read_plus_test.sh
# and its Windows link line, cross-linked
sudo make -C samples/network/nfs_retro clean
CC=x86_64-w64-mingw32-gcc make -C samples/network/nfs_retro nfs_test
make -C samples/network/nfs_retro clean
CC=x86_64-w64-mingw32-gcc make -C samples/network/tls_retro tls_fetch
make -C samples/network/tls_retro clean
- name: Built-in Kerberos client against a MIT KDC
shell: bash
working-directory: ${{ github.workspace }}
run: |
set -eu
# net_krb5.c against krb5kdc on a fixed port: AS with and
# without PA-ENC-TIMESTAMP, TGS for a cifs/ service with the
# ticket checked against the service's keytab key, wrong
# password and unknown service refused.
samples/network/krb5_retro/local_kdc_test.sh
make -C samples/network/krb5_retro clean
CC=x86_64-w64-mingw32-gcc make -C samples/network/krb5_retro krb5_test
make -C samples/network/krb5_retro clean
+123
View File
@@ -0,0 +1,123 @@
name: CI Linux without pkg-config
on:
push:
branches:
- master
pull_request:
branches:
- master
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:
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: 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: |
set -eu
# PKG_CONF_PATH=none makes configure behave as it does on a
# system with no pkg-config binary: every check_val falls
# back to a plain link test and takes its cflags from the
# include directories it is given. A library that links but
# whose headers are not on the include path must end up
# disabled, never enabled with empty cflags.
PKG_CONF_PATH=none ./configure --enable-dbus
grep -q '^HAVE_PIPEWIRE = 1' config.mk
grep -q '^HAVE_PIPEWIRE_STABLE = 1' config.mk
grep '^PIPEWIRE_CFLAGS = ' config.mk | grep -q -- '-I/usr/include/pipewire-0.3'
grep '^PIPEWIRE_CFLAGS = ' config.mk | grep -q -- '-I/usr/include/spa-0.2'
# Found without pkg-config: D-Bus by its headers, one of them
# under the library directory; Wayland by its libraries and the
# scanner's own version; Qt through qmake -query.
for have in DBUS WAYLAND WAYLAND_SCANNER LIBDECOR OPENSSL QT QT6; do
grep -q "^HAVE_$have = 1" config.mk \
|| { echo "::error::HAVE_$have not found without pkg-config"; exit 1; }
done
grep '^DBUS_CFLAGS = ' config.mk | grep -q -- '-I/usr/include/dbus-1.0'
grep '^DBUS_CFLAGS = ' config.mk | grep -q -- 'dbus-1.0/include'
- name: Build
shell: bash
run: |
set -eu
make -j"$(nproc)"
test -x ./retroarch
test -f obj-unix/release/audio/drivers/pipewire.o
test -f obj-unix/release/camera/drivers/pipewire.o
test -f obj-unix/release/gfx/common/dbus_common.o
test -f obj-unix/release/gfx/common/wayland_common.o
test -f obj-unix/release/ui/drivers/ui_qt.o
- name: The run-time libdbus mirror matches libdbus
shell: bash
run: |
set -eu
# gfx/common/dbus_runtime.h mirrors libdbus's public structs and
# constants so RetroArch can load it without its headers; the
# D-Bus CFLAGS configure found above lead to the real ones.
make -C samples/frontend/dbus_runtime check \
DBUS_CFLAGS="$(sed -n 's/^DBUS_CFLAGS = //p' config.mk)"
- name: A package forced by name fails when nothing can find it
shell: bash
run: |
set -u
# rsound is found only through pkg-config. Asked for by name with
# no pkg-config, configure must stop and say so rather than
# quietly build without it.
if PKG_CONF_PATH=none ./configure --enable-rsound > forced.log 2>&1; then
cat forced.log
echo "::error::--enable-rsound without pkg-config configured instead of failing"
exit 1
fi
grep -q 'Forced to build with package rsound, but pkg-config was not found' forced.log \
|| { cat forced.log; echo "::error::configure failed for another reason"; exit 1; }
# A forced package with a library check behind it still configures.
PKG_CONF_PATH=none ./configure --enable-pulse > forced.log 2>&1 \
|| { cat forced.log; echo "::error::--enable-pulse failed without pkg-config"; exit 1; }
grep -q '^HAVE_PULSE = 1' config.mk
+323
View File
@@ -0,0 +1,323 @@
name: CI OpenXR (stand-in runtime, desktop headset path)
on:
push:
branches:
- master
pull_request:
branches:
- master
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.
./configure --disable-qt --enable-openxr
grep -q '^HAVE_OPENXR = 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 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
grep -q '^HAVE_OPENXR = 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 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
@@ -0,0 +1,223 @@
name: CI Linux overlay on screen
# The one overlay test that looks at the screen. It builds a real
# retroarch with X11 and OpenGL, runs it under Xvfb on Mesa's software
# rasteriser with an input overlay enabled, threaded video off and then
# on, grabs the screen and checks the overlay is where its config puts
# it and nowhere else. It then checks the menu's mouse cursor with an
# overlay up: a page of "nul" buttons (an LED overlay) must leave the
# cursor to the menu, a page that takes input must not. Last, with the
# overlay LED driver on, it checks that only LED images are hidden and
# that a hidden one stays hidden while its button is held.
#
# Every stubbed overlay test passed while an enabled overlay covered the
# whole screen (2026-09-19). Nothing that stubs the driver, or builds
# the pack by hand, can see that; this can.
on:
push:
branches:
- master
paths:
- 'input/input_driver.c'
- 'input/input_overlay*'
- '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:
- 'input/input_driver.c'
- 'input/input_overlay*'
- '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:
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:
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: 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, the rest off)
shell: bash
run: |
set -eu
# What draws the overlay has to be real: X11, the gl driver,
# the menu, threads. Everything else is build time.
./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
make -j$(nproc)
ccache -s
- name: Run overlay_on_screen_test (threaded video off, then on)
shell: bash
run: |
set -eu
# Two solid magenta squares on a full-screen page. Each must
# be where the config puts it, the middle of the screen must
# not be overlay, and the overlay must cover about the area of
# its two squares. With "does this desc have an image" asked
# of pixels that had just been released, no desc's geometry
# was set and the overlay covered 100% of the screen, in both
# 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 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: |
set -eu
# The menu switched its mouse off whenever any overlay was
# alive, so an LED overlay - nothing on it to press - left a
# fullscreen desktop user with no menu cursor. A pack of
# "nul" buttons must now show the cursor where the pointer is
# moved; the same pack bound to "a" must still not.
timeout 180 python3 samples/input/overlay_on_screen/overlay_menu_mouse_test.py ./retroarch
- name: Run overlay_led_hidden_test (mapped and named LED packs, threaded off and on)
shell: bash
run: |
set -eu
# With the overlay LED driver on and Up held through XTEST: an
# image at a ledN_map slot, "nul" or a control, stays hidden,
# 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 }}
+32 -18
View File
@@ -37,28 +37,42 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Install dependencies
timeout-minutes: 5
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
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 libfreetype6-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.
+45
View File
@@ -0,0 +1,45 @@
name: CI QNX input drivers (stand-in SDK)
on:
push:
branches:
- master
pull_request:
branches:
- master
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
+27 -17
View File
@@ -32,27 +32,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: 5
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
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 \
+462 -34
View File
@@ -23,32 +23,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: 5
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
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 }}
@@ -58,11 +67,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)
@@ -116,6 +124,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
@@ -225,6 +248,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
@@ -340,8 +381,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:
@@ -398,22 +437,197 @@ 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 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)"
- 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" \
@@ -426,6 +640,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)
@@ -555,6 +774,24 @@ jobs:
./device_list_driver_test
echo "[pass] device_list_driver_test (ASan)"
- name: Build and run device_label_path_test (ASan + UBSan)
shell: bash
working-directory: samples/audio/device_label_path
run: |
set -eu
# A device list entry with the hardware's name after the path,
# "/dev/dsp0 (HDA Intel)", is stored by the menu as shown and
# comes back to init as the device string; oss opened it as a
# path and failed, so a device picked from the list could not
# be used. Compiles oss and audioio whole with open() redirected
# to a recorder, and checks each reaches it with the path.
make clean && make -j$(nproc) all SANITIZER=address,undefined
test -x device_label_path_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./device_label_path_test
echo "[pass] device_label_path_test (ASan)"
- name: Build and run rate_control_bounds_test (ASan + UBSan)
shell: bash
working-directory: samples/audio/rate_control_bounds
@@ -668,6 +905,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
@@ -733,16 +994,48 @@ 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)"
- name: Build and run wdmks_loop_test (MinGW, under wine)
shell: bash
working-directory: samples/audio/wdmks_loop
run: |
set -eu
# 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
# hardware itself, so nothing reaches the kernel and wine
# needs no sound card. Pins that every byte the cursor passes
# is cleared, so the loop plays silence when the frontend
# stops writing - a pause, a menu, a stall - instead of the
# last loop of audio round and round; that a write after
# that lands a margin ahead of the hardware rather than a
# loop behind it; that a full loop is not taken for an
# overrun; and that a start puts the model back at the top
# of the loop with the hardware; and that the resync margin is
# the FIFO the pin reports plus half a millisecond, two
# milliseconds where it reports none; and that gaps in the
# audio are counted once each, neither a start nor every
# sample of a pause.
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
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 wasapi_exclusive_pacing_test (ASan + UBSan, then TSan)
shell: bash
working-directory: samples/audio/wasapi_exclusive_pacing
@@ -880,6 +1173,89 @@ 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: |
# The SSE-only build must still pick SSE when AVX is requested.
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.
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
make clean
WINEDEBUG=-all timeout 300 make check CC=x86_64-w64-mingw32-gcc \
RUN="wine ./sinc_fastforward_test.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.
make -j$(nproc) check SANITIZER=address,undefined
make clean
WINEDEBUG=-all timeout 300 make check CC=x86_64-w64-mingw32-gcc \
RUN="wine ./sinc_sibling_test.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: |
# 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: |
@@ -887,7 +1263,59 @@ 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.
# 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.
make clean && make -j$(nproc)
test -x pipeline_clocked_test
PACED_ONLY=1 timeout 600 ./pipeline_clocked_test 3
echo "[pass] pipeline_clocked_test (fill cap)"
- 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 }}
+135 -41
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,32 +41,41 @@ 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
timeout-minutes: 25
steps:
- name: Install dependencies
timeout-minutes: 5
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
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 }}
@@ -72,11 +85,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)
@@ -84,32 +96,51 @@ 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: Build and run ffmpeg_sweep_test (plain, ASan + UBSan, TSan)
shell: bash
working-directory: samples/cores/ffmpeg_sweep
@@ -124,21 +155,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
@@ -153,13 +211,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
@@ -168,3 +226,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 }}
+104 -25
View File
@@ -23,32 +23,41 @@ jobs:
samples-formats:
name: Build and run samples/formats
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential ffmpeg clang ccache
timeout-minutes: 10
steps:
- name: Install dependencies
timeout-minutes: 5
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
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 }}
@@ -58,11 +67,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 dts_lpcm_test (plain, ASan + UBSan)
@@ -95,10 +103,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)"
@@ -135,7 +143,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
@@ -200,8 +208,79 @@ 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: VP9 sessions and WebM GPU dispatch (ASan + UBSan)
working-directory: libretro-common/samples/formats/vp9
run: |
make check stream-check pixel-check SANITIZER=address,undefined
make stack-check CC=gcc
make stack-check CC=clang
- 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 }}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+295 -27
View File
@@ -23,32 +23,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: 5
run: |
pkgs="build-essential libmbedtls-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
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 }}
@@ -58,11 +67,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)
@@ -82,16 +90,173 @@ 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)"
- name: Build and run webdav_reauth_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/webdav_reauth
run: |
set -eu
# Includes network/cloud_sync/webdav.c and plays the server
# for every request type the driver sends (OPTIONS, GET, PUT,
# DELETE, MOVE, MKCOL). A server that keeps answering 401 with
# a Digest challenge - a wrong password - must get one retry
# and then a reported failure; the driver used to retry every
# challenge, so a mistyped password looped the first request
# of a sync forever. A single stale-nonce challenge must still
# be retried and succeed. A retried PUT must send the file:
# it used to re-read it from the end the first attempt left
# it at and upload uninitialised memory in place of the save.
# 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 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 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)"
- name: Build and run webdav_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/webdav_backup
run: |
set -eu
# Includes network/cloud_sync/webdav.c and plays the server,
# request by request. With destructive sync off, an upload
# must COPY the server's file to deleted/<path>-<time> before
# the PUT replaces it - it used to overwrite it in place, so
# the setting protected against deletes but not overwrites.
# Covers the order (backup first, into an existing deleted/
# collection, named as a delete names it), 404 as "nothing to
# 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 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 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)"
- name: Build and run s3_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/s3_backup
run: |
set -eu
# Includes network/cloud_sync/s3.c and plays the server,
# request by request. The S3 driver never read the
# destructive sync setting: deletes removed objects for good
# and uploads overwrote them. With it off, both must first
# CopyObject the object to deleted/<path>-<time>. Covers the
# copy source for virtual-hosted and path-style endpoints,
# x-amz-copy-source being signed, 404 as "nothing to keep", a
# 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 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 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)"
- name: Build and run smb_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/smb_backup
run: |
set -eu
# Includes network/cloud_sync/smb.c and runs it against an
# in-memory libsmb2 share (fake_smb.c), checking what the
# share holds after each upload. The driver used to truncate
# the server file and then write, so a write failing part way
# left a truncated save; and with destructive sync off it
# overwrote the file without keeping the old copy a delete
# keeps. Uploads now go through <path>.rauploading and the
# replaced file is kept as <path>-<time> (non-destructive).
# Covers a failed write, backup and final rename each leaving
# 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 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 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
run: |
set -eu
# Includes network/cloud_sync/google_drive.c and plays the
# Drive API, request by request. With destructive sync off, a
# delete kept the old file (renamed <name>-<time>) but an
# upload replaced its content outright. An upload to an
# existing file must now files.copy it to that name in the
# same folder before the PATCH. Covers the order and the copy
# request, a failed copy stopping the upload, a 401 on the
# 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 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 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)"
- name: Build and run cheevos_login_test (bear ASan+UBSan, bear TSan, mbed ASan+UBSan)
shell: bash
working-directory: samples/network/cheevos_login
@@ -106,18 +271,121 @@ jobs:
# from a realistic messy CA bundle (BearSSL), keep-alive pool
# reuse, one-shot replay of a request on a pooled connection
# the server closed while idle, and no replay once response
# bytes have been seen.
make clean all SANITIZER=address,undefined
# bytes have been seen. Each TLS backend's non-blocking send
# must push a 12 MiB body past a server that stops reading for
# a second without ever blocking, and deliver every byte; and
# 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 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 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
echo "[pass] cheevos_login_test (built-in TLS, ASan)"
# The runner's libmbedtls-dev is 2.28, which has no TLS 1.3,
# so the leg above cannot see either TLS 1.3 failure. Mbed
# TLS 3.6.0 is the one release that shows both: it fails
# every handshake unless psa_crypto_init() was called, and it
# reports session tickets from mbedtls_ssl_read(). Library
# only, ~20 s to build. A download that still fails after
# the retries skips the leg rather than failing the job.
mbed_ver=3.6.0
mbed_sha=3ecf94fcfdaacafb757786a01b7538a61750ebd85c4b024f56ff8ba1490fcd38
mbed_tar="$RUNNER_TEMP/mbedtls-$mbed_ver.tar.bz2"
if curl -fsSL --retry 3 --retry-delay 5 --max-time 120 -o "$mbed_tar" \
"https://github.com/Mbed-TLS/mbedtls/releases/download/v$mbed_ver/mbedtls-$mbed_ver.tar.bz2"; then
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 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 \
make check SSL_BACKEND=mbed
echo "[pass] cheevos_login_test (mbedTLS $mbed_ver TLS 1.3, ASan)"
else
echo "::warning::Mbed TLS $mbed_ver download failed; TLS 1.3 leg skipped"
fi
- name: Build and run net_http_semantics_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/net_http_semantics
run: |
set -eu
# HTTP/1.1 semantics of libretro-common/net/net_http.c against
# an in-process scripted server on 127.0.0.1, which logs which
# connection every request arrived on. Covers GET 200 framing
# and pool reuse, and responses without a body: HEAD, 204 and
# 304 on keep-alive used to sit waiting for a close that never
# came, a 1xx other than 100 was taken as the final status,
# and a 101 or stray bytes after a bodyless response left the
# socket in the pool. Connection and Transfer-Encoding are
# read as lists ("Keep-Alive, Close", "gzip, chunked"),
# HTTP/1.0 without keep-alive is not pooled, and a length
# after "chunked" does not reframe the body. Redirects: an
# absolute-path Location used to go out as "GET //foo", a
# 302 loop was followed forever (now 8 hops), a streamed PUT
# redirected by a 307 is rewound and resent whole, and a 3xx
# body no longer lands in the caller's sink. Bounds: a header
# block (1xx blocks included) over 64 KiB and a chunk size
# with no hex digits fail instead of growing the buffer or
# passing as the last chunk, a Content-Length buffer never
# grows past the body, and a pooled connection that dies
# after the status line is not replayed. A URL shorter than
# its scheme is rejected without an overread. Unit matrix for
# net_http_url_join() and net_http_urldecode(), including
# buffer bounds and in-place decoding; redirects resolved
# through it ("../", "?q", "//host", fragments dropped,
# %20 left encoded). The response header block: lookup and
# iteration, the legacy string_list accessor, and 64 header
# lines costing no per-line allocations. A Content-Length or
# "chunked" on a 1xx block does not frame the final response.
# A followed 3xx's body is dropped as it arrives, not
# buffered to its Content-Length, and bytes past a body's
# Content-Length end the connection, not the transfer.
# Every case has a
# deadline, so a hang fails the case instead of the job.
# net_http is lock-free by design (DNS cache and pool owned by
# the driving thread, resolvers publish with an atomic store);
# keep it that way.
if grep -nE '\b(slock|scond)_[a-z_]+\(' ../../../libretro-common/net/net_http.c; then
echo "::error title=net_http lock::net_http.c must not take locks"
exit 1
fi
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 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 }}
+121 -23
View File
@@ -6,6 +6,7 @@ on:
- master
paths:
- 'record/**'
- 'gfx/common/videotoolbox_encode*.h'
- 'libretro-common/queues/**'
- 'libretro-common/include/retro_spsc.h'
- 'libretro-common/include/retro_atomic.h'
@@ -16,6 +17,7 @@ on:
- master
paths:
- 'record/**'
- 'gfx/common/videotoolbox_encode*.h'
- 'libretro-common/queues/**'
- 'libretro-common/include/retro_spsc.h'
- 'libretro-common/include/retro_atomic.h'
@@ -37,32 +39,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: 5
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
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 }}
@@ -72,11 +83,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)
@@ -92,12 +102,100 @@ 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
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
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: GPU PCM output pipeline (ASan + UBSan, TSan, mingw-w64)
working-directory: samples/record/gpu_av_output
run: |
set -eu
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
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
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
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 }}
+78 -22
View File
@@ -23,32 +23,41 @@ jobs:
samples-settings:
name: Build and run samples/settings
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential ccache
timeout-minutes: 15
steps:
- name: Install dependencies
timeout-minutes: 5
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
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 }}
@@ -58,13 +67,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
@@ -81,9 +112,34 @@ 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: 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
@@ -0,0 +1,104 @@
name: CI Linux samples [nothing else runs these]
on:
push:
branches:
- master
pull_request:
branches:
- master
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:
samples-unclaimed:
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: 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: |
set -eu
# A sample that builds and runs, and that no workflow names,
# is a test nobody runs: font_lifecycle sat that way while
# its expectation drifted away from the font driver it was
# written against. This job takes whatever is left over -
# the list is computed, so a new sample joins it by existing
# rather than by being remembered.
#
# Samples that need a library the runner does not carry
# (SDL, FFmpeg, libusb) are skipped: their own workflows
# install what they need. mingw-w64 and Wine come from the
# install step, for the samples cross-built and run under Wine.
fails=0
for mk in $(find samples libretro-common/samples -name Makefile | sort); do
dir=$(dirname "$mk")
name=$(basename "$dir")
(cd "$dir" && make -n check >/dev/null 2>&1) || continue
if grep -rqlF "$name" .github/workflows/ 2>/dev/null; then
continue
fi
if grep -qiE 'sdl|ffmpeg|libav|libusb' "$mk"; then
echo "-- skip $dir (needs a library this job does not install)"
continue
fi
echo "== $dir"
if (cd "$dir" && make clean >/dev/null 2>&1 \
&& ASAN_OPTIONS=detect_leaks=1:allocator_may_return_null=1 \
UBSAN_OPTIONS=print_stacktrace=1 \
timeout 300 make SANITIZER=address,undefined check); then
echo " ok"
else
echo " FAIL $dir"
fails=$((fails + 1))
fi
(cd "$dir" && make clean >/dev/null 2>&1) || true
done
if [ "$fails" -ne 0 ]; then
echo "$fails sample(s) failed"
exit 1
fi
@@ -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)
+32 -18
View File
@@ -36,28 +36,42 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Install dependencies
timeout-minutes: 5
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
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 libfreetype6-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.
+65
View File
@@ -0,0 +1,65 @@
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
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:
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
+27 -20
View File
@@ -43,32 +43,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 libfreetype6-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: 5
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
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 }}
+746 -100
View File
@@ -9,9 +9,17 @@ name: CI Vulkan/GL/D3D11 [idle waits + driver harness]
# load, font free and swapchain resize - and all of them were replaced
# by waits on the driver's own fences.
#
# 1. Idle-wait check: tools/vulkan_idle_wait_check.py fails on any
# 1. Idle-wait check: tools/vulkan_idle_wait_check.c fails on any
# vkQueueWaitIdle/vkDeviceWaitIdle in the Vulkan code that is not
# on tools/vulkan_idle_wait_allow.list, and on a stale entry.
# tools/d3d12_idle_wait_check.c holds the D3D12 driver to the same:
# a queue drain or a fence wait not on
# tools/d3d12_idle_wait_allow.list fails, so the frame paths, which
# wait only for their predecessor's own signal, stay that way. A
# ResizeBuffers with no drain before it in its function fails too:
# that signal is behind the frame's list, not its Present, and the
# swap chain resized on a software core's first frame while the
# Present still held a back buffer.
#
# 2. Validation harness: the threaded-video harness (real frontend,
# real driver, only main() replaced) run on the Vulkan driver over
@@ -46,6 +54,26 @@ name: CI Vulkan/GL/D3D11 [idle waits + driver harness]
# frontend does against the driver from the wrong thread is an X
# BadAccess here rather than a corruption elsewhere; the first run
# caught the OSD font rebuild doing exactly that.
#
# 6. Teardown-order check: tools/vulkan_teardown_order_check.c holds
# the order the driver retires a frame's objects in - command
# pools, then descriptor pools, then the pipelines that own the set
# layout - and the rule that destroying a descriptor pool is what
# frees its sets. The other order is legal Vulkan and the
# validation layer passes it, so the harness lanes above cannot see
# it; a driver that walks its own back references as a pool is
# destroyed faults inside vkDestroyDescriptorPool at exit, which is
# what MoltenVK did.
#
# 7. OpenXR build: the validation harness once more on a tree
# configured --enable-openxr, without a headset. The headset checks
# sit in the frame path's present chain, so the window path is only
# proven in a build that compiles them; the GPU readback lane is
# where a misplaced one sent a read-back frame to present twice.
#
# 8. SDL3 views: the views e2e cases on the SDL3 driver, against an SDL3
# built from a pinned release with its renderer, the tree configured
# with SDL3 in place of SDL2.
on:
push:
@@ -64,8 +92,8 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
vulkan-idle-waits:
name: Vulkan idle-wait allowlist
wait-checkers:
name: Vulkan and D3D12 wait and teardown checkers
runs-on: ubuntu-latest
timeout-minutes: 5
@@ -73,41 +101,104 @@ jobs:
- name: Checkout
uses: actions/checkout@v5
- name: Checker self-test
shell: bash
run: |
set -eu
python3 tools/vulkan_idle_wait_check.py --selftest
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Tree check
- name: Vulkan idle-wait allowlist, self-test
shell: bash
run: |
set -eu
python3 tools/vulkan_idle_wait_check.py
tools/check.sh tools/vulkan_idle_wait_check.c --selftest
- name: Vulkan idle-wait allowlist, tree check
# Each checker runs even when one before it fails.
if: ${{ !cancelled() }}
shell: bash
run: |
set -eu
tools/check.sh tools/vulkan_idle_wait_check.c
- name: D3D12 wait allowlist, self-test
if: ${{ !cancelled() }}
shell: bash
run: |
set -eu
tools/check.sh tools/d3d12_idle_wait_check.c --selftest
- name: D3D12 wait allowlist, tree check
if: ${{ !cancelled() }}
shell: bash
run: |
set -eu
tools/check.sh tools/d3d12_idle_wait_check.c
- name: Vulkan teardown order, self-test
if: ${{ !cancelled() }}
shell: bash
run: |
set -eu
tools/check.sh tools/vulkan_teardown_order_check.c --selftest
- name: Vulkan teardown order, tree check
if: ${{ !cancelled() }}
shell: bash
run: |
set -eu
tools/check.sh tools/vulkan_teardown_order_check.c
vulkan-validation-harness:
name: Threaded-video harness on Vulkan (lavapipe, validation layer)
runs-on: ubuntu-latest
timeout-minutes: 45
env:
APT_PKGS: build-essential libxkbcommon-dev libx11-xcb-dev libxcb1-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libxv-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers vulkan-tools libasound2-dev libgl1-mesa-dri libgl-dev imagemagick libsdl2-dev ccache
timeout-minutes: 75
steps:
- name: Install dependencies
timeout-minutes: 8
run: |
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev libxcb1-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libxv-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers vulkan-tools libasound2-dev"
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
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
# Non-Qt, as the harness requires (with Qt, main() lives in
# ui_qt.o). Everything else at its default so the Vulkan driver
@@ -121,8 +212,14 @@ jobs:
# debug messenger that reports through RARCH_ERR. It is a
# tree-wide define: vulkan_context_t carries a member under it,
# so every TU that includes vulkan_common.h must see it.
# GFX_INSTRUMENT counts what the image and surface paths do -
# uploads, in-place updates, allocating posts, copies - so the
# harness lanes below can hold the budgets the paths are
# written to and print what a run cost. This is the one leg
# that carries it: under a sanitizer the timings mean nothing,
# and the counters change no behaviour.
run: |
make -j$(getconf _NPROCESSORS_ONLN) VULKAN_DEBUG=1
make -j$(getconf _NPROCESSORS_ONLN) VULKAN_DEBUG=1 GFX_INSTRUMENT=1
test -x retroarch
- name: Run the harness on Vulkan under the validation layer
@@ -142,33 +239,212 @@ jobs:
# lines, so it has to name the same build: VK_DEBUG=1. Its
# surface and overlay-page lanes run the driver's real
# in-place texture update and its borrowed-texture overlay
# page here, under the validation layer.
SAN=none VK_DEBUG=1 CYCLES=2 make -C samples/gfx/threaded_video check-vulkan
# page here, under the validation layer, and with the tree
# built GFX_INSTRUMENT=1 they print their baselines and hold
# the budgets: one texture per streaming surface, no
# allocation per post, no copy, a 4K submit that stays a
# descriptor hand-off.
SAN=none VK_DEBUG=1 GFX_INSTRUMENT=1 CYCLES=2 make -C samples/gfx/threaded_video check-vulkan
# Once more with the harness core pushing XRGB8888: the
# driver samples a linear 32-bit image itself where it
# converts RGB565 with a compute pass, so a lent frame and
# a window into it take the other upload path (the one
# beetle-psx takes).
HARNESS_CORE_XRGB8888=1 SAN=none VK_DEBUG=1 GFX_INSTRUMENT=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan
# And with the first map of a buffer-chain block refused on
# each device: the driver must not suballocate from a buffer
# it could not map.
SAN=none VK_DEBUG=1 GFX_INSTRUMENT=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan-mapfail
# And with the harness core as a Vulkan hardware core, which
# runs its own lanes alone: the core's image must not be drawn
# from once the core has destroyed it - at a content close,
# unthreaded and under the wrapper, and after a set_image(NULL)
# - a dupe of a hardware frame must not re-submit a fence that
# is already signalled, and a frame the push replaces must not
# leave its semaphore signalled for the core to signal again.
# Each is a validation error here, and on lavapipe a crash.
HARNESS_CORE_HW_VULKAN=1 SAN=none VK_DEBUG=1 GFX_INSTRUMENT=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan
make -C samples/gfx/threaded_video clean
# And as a core that creates the device itself, through the
# negotiation interface's create_device - Beetle PSX, for one.
# That device has the extensions the frontend requires and
# none of the optional ones it adds to a device of its own, so
# anything the driver or the filter chain does that leans on
# an optional extension is a validation error here and nowhere
# else. The filter chain's mirror-clamp samplers were.
HARNESS_CORE_HW_VULKAN=2 SAN=none VK_DEBUG=1 GFX_INSTRUMENT=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan
make -C samples/gfx/threaded_video clean
# And as a core that caches its context across a video reinit
# (Azahar): the instance comes back from the cache with what
# it was created with, HDR support included.
HARNESS_CORE_HW_VULKAN=3 SAN=none VK_DEBUG=1 GFX_INSTRUMENT=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan
make -C samples/gfx/threaded_video clean
- name: Views on gl, glcore, Vulkan and SDL2, end to end (Xvfb, llvmpipe, lavapipe)
shell: bash
run: |
set -eu
# The views compositor (gfx/video_views_draw.h) as gl, glcore,
# Vulkan and SDL2 instantiate it: the video_views test core's map in
# each stereo mode, a view per eye, the blend canvas, views
# read exactly out of the frame in every way the drivers copy
# or draw them, the UI layer per eye with the menu open, and a
# pointer mapped back through it. Each case runs on an Xvfb of
# its own; Vulkan here under the validation layer.
make -C samples/cores/video_views
export VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.json
export LIBGL_ALWAYS_SOFTWARE=1
RA_E2E_XVFB=1 RA_E2E_ONLY='^(3ds-sbs-full|3ds-sbs-half|3ds-top-bottom|3ds-anaglyph|3ds-interlaced|ds-horizontal|exact-3ds|crop-threaded-menu|pointer-sbs-full)$' \
python3 samples/cores/video_views/e2e/run.py ./retroarch \
"$RUNNER_TEMP/views-e2e" gl glcore vulkan sdl2
- 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 }}
vulkan-validation-openxr:
name: Threaded-video harness on Vulkan built with OpenXR (lavapipe, validation layer)
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential libxkbcommon-dev libx11-xcb-dev libxcb1-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libxv-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers vulkan-tools libasound2-dev ccache
timeout-minutes: 45
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: Configure
# OpenXR is opt-in. With it the frame path carries the headset
# checks in its present chain while the window still presents,
# which the default build above never compiles.
run: |
./configure --disable-qt --enable-openxr
grep -q '^HAVE_VULKAN = 1' config.mk
grep -q '^HAVE_OPENXR = 1' config.mk
- name: Build with VULKAN_DEBUG=1
run: |
make -j$(getconf _NPROCESSORS_ONLN) VULKAN_DEBUG=1
test -x retroarch
- name: Run the harness on Vulkan under the validation layer
shell: bash
run: |
set -eu
Xvfb :99 -screen 0 640x480x24 &
for i in $(seq 1 15); do
if DISPLAY=:99 xdpyinfo >/dev/null 2>&1; then
break
fi
sleep 1
done
export DISPLAY=:99
export VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.json
SAN=none VK_DEBUG=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan
make -C samples/gfx/threaded_video 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 }}
vulkan-validation-sample:
name: vulkan_validation sample (lavapipe, validation layer)
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential libx11-dev libx11-xcb-dev libxcb1-dev xvfb x11-utils libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers vulkan-tools
timeout-minutes: 15
steps:
- name: Install dependencies
timeout-minutes: 8
run: |
pkgs="build-essential libx11-dev libx11-xcb-dev libxcb1-dev xvfb x11-utils libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers vulkan-tools"
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
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: Build and run
shell: bash
run: |
@@ -198,24 +474,59 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Install dependencies
timeout-minutes: 8
run: |
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev libxcb1-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libxv-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers vulkan-tools libasound2-dev"
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
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 libxcb1-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libxv-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers vulkan-tools libasound2-dev ccache
# 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: 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-qt
@@ -240,31 +551,82 @@ jobs:
export DISPLAY=:99
export VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.json
SAN=address VK_DEBUG=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan
HARNESS_CORE_XRGB8888=1 SAN=address VK_DEBUG=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan
HARNESS_CORE_HW_VULKAN=1 SAN=address VK_DEBUG=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan
make -C samples/gfx/threaded_video 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 }}
vulkan-tsan-harness:
name: Threaded-video harness on Vulkan under TSan (lavapipe)
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Install dependencies
timeout-minutes: 8
run: |
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev libxcb1-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libxv-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers vulkan-tools libasound2-dev"
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
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 libxcb1-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libxv-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers vulkan-tools libasound2-dev ccache
# 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: 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-qt
@@ -291,42 +653,104 @@ jobs:
SAN=thread VK_DEBUG=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan
make -C samples/gfx/threaded_video 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 }}
gl-harness:
name: Threaded-video harness on ${{ matrix.driver }} (llvmpipe, ${{ matrix.san }})
name: Threaded-video harness on ${{ matrix.drivers }} (llvmpipe, ${{ matrix.api }}, ${{ matrix.san }})
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential libxkbcommon-dev libx11-xcb-dev libxcb1-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libxv-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libgl-dev libglx-dev libegl-dev libgles-dev libgl1-mesa-dri libglx-mesa0 mesa-utils libasound2-dev ccache
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
driver: [gl, glcore]
san: [none, address, thread]
# One build per API and sanitizer, the harness run on each
# driver it serves. The GLES 3 build is the Android one: frame
# memory and the framebuffer loan through EXT_buffer_storage,
# which llvmpipe's GLES 3.2 has, and the harness fails a GL
# driver without them.
include:
- { api: desktop, san: none, drivers: gl glcore gl1 }
- { api: desktop, san: address, drivers: gl glcore gl1 }
- { api: desktop, san: thread, drivers: gl glcore gl1 }
- { api: gles3, san: none, drivers: gl glcore }
- { api: gles3, san: address, drivers: gl }
- { api: gles3, san: thread, drivers: glcore }
steps:
- name: Install dependencies
timeout-minutes: 8
run: |
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev libxcb1-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libxv-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libgl-dev libglx-dev libegl-dev libgles-dev libgl1-mesa-dri libglx-mesa0 mesa-utils libasound2-dev"
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
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
run: |
./configure --disable-qt
if [ "${{ matrix.api }}" = gles3 ]; then
./configure --disable-qt --enable-opengles --enable-opengles3 \
--disable-opengl1
grep -q '^HAVE_OPENGLES3 = 1' config.mk
else
./configure --disable-qt
fi
grep -q '^HAVE_OPENGL = 1' config.mk
grep -q '^HAVE_OPENGL_CORE = 1' config.mk
- name: Build
run: |
# The sanitizer builds are DEBUG=1: -g1, as in the other jobs.
[ "${{ matrix.san }}" = none ] || export CFLAGS="-O0 -g1" CXXFLAGS="-O0 -g1"
case "${{ matrix.san }}" in
thread) make -j$(getconf _NPROCESSORS_ONLN) SANITIZER=thread DEBUG=1 ;;
address) make -j$(getconf _NPROCESSORS_ONLN) SANITIZER=address,undefined DEBUG=1 ;;
@@ -334,7 +758,7 @@ jobs:
esac
test -x retroarch
- name: Run the harness on ${{ matrix.driver }}
- name: Run the harness on ${{ matrix.drivers }}
shell: bash
run: |
set -eu
@@ -347,9 +771,33 @@ jobs:
done
export DISPLAY=:99
export LIBGL_ALWAYS_SOFTWARE=1
SAN=${{ matrix.san }} DRIVER=${{ matrix.driver }} CYCLES=1 \
make -C samples/gfx/threaded_video check-driver
make -C samples/gfx/threaded_video clean
# The harness takes its compile line from the build's own.
[ "${{ matrix.san }}" = none ] || export CFLAGS="-O0 -g1" CXXFLAGS="-O0 -g1"
# Every driver runs, so one failing does not hide another.
fail=0
for driver in ${{ matrix.drivers }}; do
echo "::group::$driver"
SAN=${{ matrix.san }} DRIVER=$driver CYCLES=1 \
make -C samples/gfx/threaded_video check-driver ||
{ echo "::error::the harness failed on $driver"; fail=1; }
make -C samples/gfx/threaded_video clean
echo "::endgroup::"
done
exit $fail
- 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 }}
d3d-harness:
name: Threaded-video harness on ${{ matrix.driver }} (WARP, MSYS2 MinGW64)
@@ -362,8 +810,15 @@ jobs:
# d3d12 runs the same lanes on the other explicit-API backend:
# its in-place texture update is fence-tagged against the frame
# that last copied from the upload buffer, which only a running
# driver exercises.
# driver exercises. It runs with the harness core pushing
# XRGB8888: d3d12 lends its framebuffer only for a row pitch
# on a 256-byte boundary, which 320 pixels reach at 4 bytes and
# not at 2, so that is the run in which a frame is lent and a
# window into the loan is read out of it (lent-window lane).
driver: [d3d11, d3d12]
include:
- driver: d3d12
core_xrgb8888: 1
steps:
- name: Checkout
@@ -373,12 +828,24 @@ jobs:
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
install: >-
base-devel
git
pacboy: >-
cc:p
gdb: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.
p=$MINGW_PACKAGE_PREFIX
pkgs="base-devel git $p-cc $p-gdb"
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, serial downloads"
sed -i -e '/^#*ParallelDownloads/d' -e '/^\[options\]/a ParallelDownloads = 1' /etc/pacman.conf
sleep $((30 * i))
done
exit 1
- name: Configure and build RetroArch (no Qt)
shell: msys2 {0}
@@ -395,5 +862,184 @@ jobs:
run: |
set -eu
samples/gfx/threaded_video/build.sh
SAN=none DRIVER=${{ matrix.driver }} CYCLES=1 GDB=1 make -C samples/gfx/threaded_video check-driver
HARNESS_CORE_XRGB8888=${{ matrix.core_xrgb8888 || '' }} \
SAN=none DRIVER=${{ matrix.driver }} CYCLES=1 GDB=1 make -C samples/gfx/threaded_video check-driver
make -C samples/gfx/threaded_video clean
views-sdl3:
name: Views on SDL3, end to end (Xvfb, llvmpipe)
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential ninja-build cmake xvfb x11-utils imagemagick libx11-dev libxext-dev libxrandr-dev libxi-dev libxss-dev libxcursor-dev libxfixes-dev libxkbcommon-dev libgl1-mesa-dri libgl-dev libegl-dev zlib1g-dev libfreetype6-dev libasound2-dev
timeout-minutes: 45
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 SDL3
# The runner image ships no libsdl3-dev: a pinned release, with
# the renderer the driver draws through.
run: |
set -eu
SDL3_VER=3.2.20
curl -sL -o /tmp/sdl3.tar.gz \
"https://github.com/libsdl-org/SDL/releases/download/release-$SDL3_VER/SDL3-$SDL3_VER.tar.gz"
tar -C /tmp -xzf /tmp/sdl3.tar.gz
cmake -S "/tmp/SDL3-$SDL3_VER" -B /tmp/sdl3-build -G Ninja \
-DCMAKE_BUILD_TYPE=Release -DSDL_SHARED=ON -DSDL_STATIC=OFF \
-DSDL_TESTS=OFF -DSDL_EXAMPLES=OFF -DSDL_AUDIO=OFF -DSDL_CAMERA=OFF \
-DSDL_WAYLAND=OFF -DSDL_X11=ON -DSDL_RENDER=ON -DSDL_OPENGL=ON \
-DSDL_OPENGLES=ON -DSDL_VULKAN=OFF -DCMAKE_INSTALL_PREFIX=/opt/sdl3 \
> /tmp/sdl3-cmake.log
ninja -C /tmp/sdl3-build > /tmp/sdl3-build.log
sudo ninja -C /tmp/sdl3-build install > /tmp/sdl3-install.log
- name: Build retroarch with SDL3
run: |
set -eu
export PKG_CONFIG_PATH=/opt/sdl3/lib/pkgconfig
./configure --enable-sdl3 --disable-sdl2 --disable-sdl --disable-qt \
--disable-vulkan --disable-ffmpeg
grep -q '^HAVE_SDL3 = 1' config.mk
make -j$(getconf _NPROCESSORS_ONLN)
make -C samples/cores/video_views
- name: Pointer helper connection regression
run: python3 samples/cores/video_views/e2e/pointer_test.py
- name: Views on SDL3, end to end
shell: bash
run: |
set -eu
# The views compositor as the SDL3 driver instantiates it: each
# view written straight into a texture of its own, the
# interlaced canvas, views read exactly out of the frame, the
# UI layer per eye with the menu open, and a pointer mapped
# back through it. Anaglyph draws the frame whole there.
export LIBGL_ALWAYS_SOFTWARE=1
export LD_LIBRARY_PATH=/opt/sdl3/lib
RA_E2E_XVFB=1 RA_E2E_ONLY='^(3ds-sbs-full|3ds-sbs-half|3ds-top-bottom|3ds-interlaced|ds-horizontal|exact-3ds|exact-3ds-rgb565|crop-threaded-menu|pointer-sbs-full)$' \
python3 samples/cores/video_views/e2e/run.py ./retroarch \
"$RUNNER_TEMP/views-e2e" sdl3
- name: Preserve views logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: views-sdl3-logs
path: ${{ runner.temp }}/views-e2e/**/*.log
if-no-files-found: ignore
views-sdl1:
name: Views on SDL 1.2, XShm and XVideo, end to end (Xvfb, sdl12-compat)
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential xvfb x11-utils imagemagick openbox libx11-dev libxext-dev libxv-dev libsdl1.2-dev libgl1-mesa-dri libgl-dev zlib1g-dev libfreetype6-dev libasound2-dev
timeout-minutes: 45
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 retroarch with SDL 1.2, XShm and XVideo
run: |
set -eu
./configure --enable-sdl --disable-sdl2 --disable-sdl3 --enable-xshm \
--enable-xvideo --disable-qt --disable-vulkan --disable-ffmpeg
grep -q '^HAVE_SDL = 1' config.mk
grep -q '^HAVE_XSHM = 1' config.mk
grep -q '^HAVE_XVIDEO = 1' config.mk
make -j$(getconf _NPROCESSORS_ONLN)
make -C samples/cores/video_views
make -C samples/gfx/xv_standin
- name: Views on SDL 1.2 and XShm, end to end
shell: bash
run: |
set -eu
# The CPU drivers' views: each view scaled straight out of the
# frame into the window's surface or image, the anaglyph and
# interlaced canvas blended on the CPU, views read exactly out
# of the frame, and the menu with it open. XShm ("x11") also
# has the pointer cases; SDL 1.2 runs under openbox, which
# sizes sdl12-compat's fullscreen window.
RA_E2E_XVFB=1 python3 samples/cores/video_views/e2e/run.py \
./retroarch "$RUNNER_TEMP/views-e2e" sdl x11
- name: Views on XVideo, end to end
shell: bash
run: |
set -eu
# Xvfb has no XVideo adaptor: samples/gfx/xv_standin's,
# preloaded, takes the image and scales it to the window.
# Views are converted straight from the frame into the
# image, packed (YUY2, UYVY) and planar (I420); the stand-in
# aborts on a put that reads outside the image.
export RA_E2E_XVFB=1
export RA_E2E_XV_STANDIN="$PWD/samples/gfx/xv_standin/libxv_standin.so"
XV_STANDIN_FORMAT=YUY2 python3 samples/cores/video_views/e2e/run.py \
./retroarch "$RUNNER_TEMP/views-e2e/yuy2" xvideo
XV_STANDIN_FORMAT=I420 python3 samples/cores/video_views/e2e/run.py \
./retroarch "$RUNNER_TEMP/views-e2e/i420" xvideo
XV_STANDIN_FORMAT=UYVY RA_E2E_ONLY='3ds-2d|anaglyph|interlaced|pointer-sbs-half' \
python3 samples/cores/video_views/e2e/run.py \
./retroarch "$RUNNER_TEMP/views-e2e/uyvy" xvideo
- name: Preserve views logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: views-sdl1-logs
path: ${{ runner.temp }}/views-e2e/**/*.log
if-no-files-found: ignore
+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 }}
+79
View File
@@ -0,0 +1,79 @@
name: CI builtin media player
on:
push:
branches: [master]
paths:
- 'cores/libretro-media/**'
- 'samples/cores/webm_player/**'
- 'libretro-common/samples/formats/audio_transfer/**'
- '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/**'
- 'libretro-common/samples/formats/audio_transfer/**'
- '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
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
make -j2 check CC=x86_64-w64-mingw32-gcc OS=Windows_NT EXE=.exe SAMPLE_OBJDIR=obj-win RUNNER=wine
+6
View File
@@ -33,6 +33,12 @@ jobs:
run: |
./configure --disable-qt --enable-xdelta
- name: Include order
run: |
# The tree's own headers are found before same-named ones in the
# system include dirs configure or pkg-config put on the line.
sh tools/include_order_check.sh
- name: Compile RA
run: |
make -j$(getconf _NPROCESSORS_ONLN) clean
+40
View File
@@ -62,3 +62,43 @@ jobs:
${{ matrix.version == 'UWP' }}:
pkg/msvc-uwp/AppPackages/RetroArch-msvcUWP
msvc2026:
runs-on: windows-2025
timeout-minutes: 90
strategy:
matrix:
version: [UWP, 2026]
configuration: [Debug, Release]
platform: [x64]
exclude:
- version: UWP
configuration: Debug
include:
- version: 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${{ matrix.version == 'UWP' && '-uwp' || ''}}"
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}}-vs2026-${{matrix.configuration}}-${{matrix.platform}}-${{ steps.slug.outputs.sha8 }}
path: |
${{ matrix.version != 'UWP' }}:
pkg/msvc/${{matrix.platform}}/${{matrix.configuration}}/RetroArch-msvc${{matrix.version}}.exe
${{ matrix.version == 'UWP' }}:
pkg/msvc-uwp/AppPackages/RetroArch-msvcUWP
+33 -31
View File
@@ -29,8 +29,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,42 +47,40 @@ 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: Collect DLLs and binaries
+43 -1
View File
@@ -65,12 +65,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,193 @@
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/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/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 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,121 @@
name: CI VideoToolbox HEVC and Metal
on:
push:
branches: [master]
paths:
- '.github/workflows/MacOS-videotoolbox-hevc.yml'
- 'samples/gfx/videotoolbox_hevc/**'
- 'samples/gfx/videotoolbox_vp9/**'
- 'samples/gfx/videotoolbox_av1/**'
- 'gfx/common/videotoolbox_av1*.h'
- 'gfx/common/videotoolbox_vp9*.h'
- 'gfx/common/vp9_color.h'
- 'libretro-common/include/formats/rvp9*.h'
- 'libretro-common/include/formats/rwebm*.h'
- 'libretro-common/formats/vp9/**'
- 'libretro-common/formats/webm/**'
- 'samples/gfx/metal_frame/**'
- 'gfx/common/videotoolbox_hevc*.h'
- 'gfx/common/metal_frame*.h'
- 'gfx/common/metal_video_decoder_driver.h'
- 'gfx/common/metal_decoder_snapshot.h'
- '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/formats/video_player.c'
- 'libretro-common/samples/formats/video_player/**'
- 'libretro-common/rthreads/**'
- 'libretro-common/include/**'
pull_request:
paths:
- '.github/workflows/MacOS-videotoolbox-hevc.yml'
- 'samples/gfx/videotoolbox_hevc/**'
- 'samples/gfx/videotoolbox_vp9/**'
- 'samples/gfx/videotoolbox_av1/**'
- 'gfx/common/videotoolbox_av1*.h'
- 'gfx/common/videotoolbox_vp9*.h'
- 'gfx/common/vp9_color.h'
- 'libretro-common/include/formats/rvp9*.h'
- 'libretro-common/include/formats/rwebm*.h'
- 'libretro-common/formats/vp9/**'
- 'libretro-common/formats/webm/**'
- 'samples/gfx/metal_frame/**'
- 'gfx/common/videotoolbox_hevc*.h'
- 'gfx/common/metal_frame*.h'
- 'gfx/common/metal_video_decoder_driver.h'
- 'gfx/common/metal_decoder_snapshot.h'
- '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/formats/video_player.c'
- 'libretro-common/samples/formats/video_player/**'
- '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: Player cold hardware startup and cancellation
run: |
for threads in 0 1; do
make -C libretro-common/samples/formats/video_player clean all THREADS="$threads" SANITIZER=address,undefined
libretro-common/samples/formats/video_player/video_player_test
done
- name: VideoToolbox VP9 configuration conformance (no device claim)
run: |
make -C samples/gfx/videotoolbox_vp9 check CC=clang SANITIZER=address,undefined
CC=clang python3 samples/gfx/videotoolbox_vp9/run.py --native
- name: VideoToolbox VP9 native pixels, Metal playback and retained output
run: |
rc=0
CC=clang python3 samples/gfx/videotoolbox_vp9/run.py --native --pixels --pipeline || rc=$?
if [ "$rc" = 77 ]; then
echo "::warning::VP9 hardware or Metal device unavailable; native VP9 playback acceptance remains incomplete"
exit 0
fi
exit "$rc"
- name: VideoToolbox AV1 configuration and ownership (no decoder device claim)
run: |
make -C samples/gfx/videotoolbox_av1 check CC=clang SANITIZER=address,undefined
CC=clang python3 samples/gfx/videotoolbox_av1/run.py --native-format
- name: VideoToolbox AV1 hardware pixels, keyframe seeks and retained images
run: |
if ! command -v ffmpeg >/dev/null ||
! ffmpeg -hide_banner -encoders 2>/dev/null | grep ' libaom-av1 ' >/dev/null; then
brew install ffmpeg-full
export PATH="$(brew --prefix ffmpeg-full)/bin:$PATH"
fi
ffmpeg -hide_banner -encoders 2>/dev/null | grep ' libaom-av1 '
rc=0
CC=clang python3 samples/gfx/videotoolbox_av1/run.py --native-pixels --pipeline || rc=$?
if [ "$rc" = 77 ]; then
echo "::warning::AV1 hardware unavailable; native AV1 decode acceptance remains incomplete"
exit 0
fi
exit "$rc"
- 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: Metal movie import, history and GPU pixel acceptance
if: ${{ !cancelled() }}
run: python3 samples/gfx/metal_frame/run.py --native
+95
View File
@@ -32,6 +32,68 @@ jobs:
# companion, the grid, the tables, the dialogs - as AppKit does them.
tools/companion_cocoa_test_macos.sh 2>&1 | grep -v "^$"
- name: Metal texture behaviour the surface, overlay and lent-framebuffer paths assume
run: |
set -o pipefail
# The driver's Metal paths have only ever been compiled. This
# asserts the two behaviours they are built on, on the runner's
# real device: a texture replaced in place while GPU work that
# samples it is in flight - the animated preview's steady
# state, and the one path with no fence check or DO_NOT_WAIT to
# drop a frame with - and an overlay page holding textures the
# pack owns, where a page switch must not take them with it.
#
# 77 means the runner exposed no Metal device; the sample says
# so rather than passing quietly.
cd samples/gfx/metal_texture_update
make check; rc=$?
if [ "$rc" = 77 ]; then
echo "::warning::no Metal device on this runner; the check did not run"
exit 0
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
# Includes network/cloud_sync/icloud_drive.m, with the iCloud
# container redirected to a temporary directory. Cloud sync
# waits for every request to report exactly once: a read whose
# local file could not be created never reported (the sync
# hung), and a cloud file that could not be loaded or a local
# file that could not be read reported twice. With iCloud
# unavailable, read and delete reported success. Also covers
# the ordinary read, missing file, upload and delete paths.
cd samples/network/icloud_drive
make clean check SANITIZER=address,undefined
- name: iCloud (CloudKit) cloud sync driver
run: |
set -o pipefail
# Includes network/cloud_sync/icloud.m, with CloudKit replaced by
# an in-memory database (+[CKContainer defaultContainer] is
# overridden in a category; real CKRecord/CKAsset/CKQuery). A
# read whose local file could not be written, or whose record
# held no readable data, reported success; an upload whose
# lookup failed created a duplicate record; a delete reported
# success before its deletes were sent, whatever became of
# them; and non-destructive sync was ignored. Checks each case,
# that deletes and uploads keep <path>-<time> with destructive
# sync off (and not for destructive sync or the manifest), and
# that every request reports exactly once.
cd samples/network/icloud_cloudkit
make clean check SANITIZER=address,undefined
- name: Compile RA
run: |
set -o pipefail
@@ -46,3 +108,36 @@ jobs:
name: RetroArch-${{ steps.slug.outputs.sha8 }}
path: |
build/Build/Products/Release
# The threaded-video harness on the Metal driver: the real frontend
# under Cocoa's own main (see samples/gfx/threaded_video/harness_cocoa.m),
# every lane the Vulkan and D3D legs run, on the runner's GPU. A
# Makefile build, since the harness links the tree's objects.
metal-harness:
runs-on: macos-latest
timeout-minutes: 60
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
./configure --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 2>&1 | tail -5
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 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
+30 -4
View File
@@ -55,6 +55,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 +71,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 +83,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 +97,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 +110,27 @@ 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: 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
+5 -2
View File
@@ -26,6 +26,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 +40,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 +54,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
+27 -14
View File
@@ -21,23 +21,41 @@ jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-orbis:latest
options: --user root
# The image is Alpine 3.15, whose musl cannot load the runner's
# Node 24, so JavaScript actions run on the host and only the
# build steps run inside the image.
env:
ORBIS_IMAGE: reallibretroretroarch/libretro-build-orbis:latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
- 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: |
apk add ncurses-dev make bash python2
apk add libintl icu-dev wget
wget https://dot.net/v1/dotnet-install.sh && chmod 755 dotnet-install.sh && ./dotnet-install.sh -c 3.0 --install-dir ~/cli
if [ -n "$DOCKERHUB_USERNAME" ] && [ -n "$DOCKERHUB_TOKEN" ]; then
echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
fi
- name: Compile RA
run: |
export PATH=~/cli:$PATH # .net cli
make -f Makefile.orbis -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1 info all
docker run --rm --user root -v "$PWD":/src -w /src "$ORBIS_IMAGE" bash -c '
set -e
apk add ncurses-dev make bash python2
apk add libintl icu-dev wget
wget https://dot.net/v1/dotnet-install.sh && chmod 755 dotnet-install.sh && ./dotnet-install.sh -c 3.0 --install-dir ~/cli
export PATH=~/cli:$PATH # .net cli
make -f Makefile.orbis -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1 info all
clang --version
orbis-ld --version
'
- name: Get short SHA
id: slug
@@ -50,8 +68,3 @@ jobs:
retroarch_orbis.elf
retroarch_orbis.oelf
retroarch_orbis.self
- name: Version of binaries
run: |
clang --version
orbis-ld --version
+44 -1
View File
@@ -33,10 +33,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)"
+57
View File
@@ -0,0 +1,57 @@
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
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
+114
View File
@@ -0,0 +1,114 @@
name: CI removed struct members
# When a struct member goes away, the compiler finds every reader it
# compiles. The readers it does not compile are the ones no Linux box
# sees - the Objective-C drivers above all, and anything behind a
# platform #ifdef - so each of those is found by whichever job builds
# that platform, one round trip at a time.
#
# window_position_x and window_position_y went that way. Packing the
# pair into one word updated the Win32 and SDL3 users, which is what a
# grep of the .c and .h files turns up; ui_cocoa.m kept reading both
# and took out the macOS job and both PowerPC cross jobs at once. Those
# three are among the slowest jobs in the tree, and the answer they
# eventually gave was four lines in one file.
#
# tools/packed_field_check.py does not cover this. It knows the six
# names a size or origin pair is spelled with, and window_position_x is
# not one of them; on the broken tree it printed 1184 lines, none of
# them ui_cocoa.m. The question here is not what a member is called,
# it is whether anything still reaches for one the change took away.
#
# So this lane reads the change rather than the tree's vocabulary: the
# members a push or a pull request removes, minus the ones something
# still declares, against every reader in every language the tree is
# written in. It reads files rather than building them, so one job
# covers what the platform jobs between them would only find one at a
# time.
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:
removed-member-check:
name: readers of a removed member
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v5
with:
# 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
run: |
set -eu
# Fixture pass: a member removed along with its readers is
# clean, one left behind in a language no lane here compiles
# is caught, and a member that merely moved, one whose name
# another struct still declares, one that was renamed, one
# named only in a comment or a log string, one declared in a
# declarator list, and a name too common to reason about are
# each left alone.
#
# 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.
tools/check.sh tools/removed_member_check.c --selftest
- name: Range check
shell: bash
run: |
set -eu
# A pull request is measured against its base; a push against
# what was there before it. A force push or a first push
# leaves 'before' as zeroes, and a merge has more than one
# parent, so fall back to the first parent in either case.
if [ -n "${PR_BASE:-}" ]; then
base="$PR_BASE"; head="$PR_HEAD"
else
base="${EVENT_BEFORE:-}"; head="$GITHUB_SHA"
case "$base" in
''|*[!0]*) ;;
*) base= ;;
esac
if [ -z "$base" ] || ! git cat-file -e "$base^{commit}" 2>/dev/null; then
base="$(git rev-parse "$head^" 2>/dev/null || true)"
fi
fi
if [ -z "$base" ]; then
echo "no base commit to compare against; nothing to check"
exit 0
fi
echo "range: $base..$head"
# 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.
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 }}
EVENT_BEFORE: ${{ github.event.before }}
@@ -0,0 +1,44 @@
name: CI Sample object directories
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:
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
+3 -1
View File
@@ -23,6 +23,9 @@ jobs:
with:
submodules: recursive
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: archive
id: archive
run: |
@@ -34,7 +37,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
+44 -1
View File
@@ -28,10 +28,53 @@ jobs:
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)"
+46 -3
View File
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-devkitpro:latest
image: devkitpro/devkitppc:latest
options: --user root
steps:
@@ -31,9 +31,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 }}
+118 -4
View File
@@ -6,6 +6,18 @@ on:
pull_request:
repository_dispatch:
types: [run_build]
release:
types: [published]
workflow_dispatch:
inputs:
release_tag:
description: Existing release tag to build (empty builds the selected ref)
type: string
default: ''
publish_release:
description: Upload the ARM64 package to the existing release
type: boolean
default: false
permissions:
contents: read
@@ -14,13 +26,15 @@ 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' }}
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || inputs.release_tag || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
msvc:
runs-on: windows-11-arm
timeout-minutes: 90
outputs:
release_tag: ${{ steps.source.outputs.release_tag }}
strategy:
matrix:
version: [2022]
@@ -29,6 +43,29 @@ jobs:
steps:
- uses: actions/checkout@v5
with:
ref: ${{ github.event.release.tag_name || inputs.release_tag || github.ref }}
- name: Record and verify the source revision
id: source
shell: pwsh
env:
RELEASE_TAG: ${{ github.event.release.tag_name || inputs.release_tag }}
PUBLISH_RELEASE: ${{ github.event_name == 'release' || inputs.publish_release == true }}
run: |
$revision = git rev-parse HEAD
if ($LASTEXITCODE) { throw 'Could not resolve the source revision.' }
"revision=$revision" >> $env:GITHUB_OUTPUT
if ($env:PUBLISH_RELEASE -eq 'true' -and !$env:RELEASE_TAG) {
throw 'Publishing requires an existing release tag.'
}
if ($env:RELEASE_TAG) {
$tagRevision = git rev-parse --verify "refs/tags/$($env:RELEASE_TAG)^{commit}"
if ($LASTEXITCODE -or $tagRevision -ne $revision) {
throw 'The checkout is not the requested release tag.'
}
}
"release_tag=$env:RELEASE_TAG" >> $env:GITHUB_OUTPUT
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1
@@ -40,11 +77,88 @@ jobs:
- name: Get short SHA
id: slug
shell: powershell
run: echo "sha8=$('${{ github.sha }}'.Substring(0,8))" >> $env:GITHUB_OUTPUT
shell: pwsh
env:
SOURCE_REVISION: ${{ steps.source.outputs.revision }}
run: echo "sha8=$($env:SOURCE_REVISION.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: |
pkg/msvc/${{ matrix.platform }}/${{ matrix.configuration }}/RetroArch-msvc${{ matrix.version }}.exe
- name: Checkout packaging tools from the workflow revision
if: matrix.configuration == 'Release'
uses: actions/checkout@v5
with:
ref: ${{ github.workflow_sha }}
path: .arm64-packaging
sparse-checkout: pkg/msvc/packaging
- name: Checkout pinned Ozone assets
if: matrix.configuration == 'Release'
uses: actions/checkout@v5
with:
repository: libretro/retroarch-assets
ref: 73106363e14e34c08a5854b4cfbc29f184e3b783
path: .arm64-assets
sparse-checkout: |
ozone
pkg
fonts
- name: Checkout pinned core information
if: matrix.configuration == 'Release'
uses: actions/checkout@v5
with:
repository: libretro/libretro-super
ref: 0532bd63ce3072f50ca4f4ada40d42aef565bd3f
path: .arm64-info
sparse-checkout: dist/info
- name: Verify and package ARM64 frontend
if: matrix.configuration == 'Release'
shell: pwsh
env:
SOURCE_REVISION: ${{ steps.source.outputs.revision }}
run: |
./.arm64-packaging/pkg/msvc/packaging/package-arm64.ps1 `
-Executable ./pkg/msvc/ARM64/Release/RetroArch-msvc2022.exe `
-SourceDirectory . -AssetsDirectory ./.arm64-assets -InfoDirectory ./.arm64-info `
-OutputDirectory ./arm64-dist -SourceRevision $env:SOURCE_REVISION `
-Repository $env:GITHUB_REPOSITORY `
-AssetsRevision 73106363e14e34c08a5854b4cfbc29f184e3b783 `
-InfoRevision 0532bd63ce3072f50ca4f4ada40d42aef565bd3f
- name: Upload ARM64 release package
if: matrix.configuration == 'Release'
uses: actions/upload-artifact@v4
with:
name: retroarch-windows-arm64-package
path: |
arm64-dist/RetroArch-WinARM64.zip
arm64-dist/RetroArch-WinARM64.zip.sha256
if-no-files-found: error
upload-release:
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.publish_release == true)
needs: msvc
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
name: retroarch-windows-arm64-package
path: arm64-dist
- name: Attach ARM64 package to the existing release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.msvc.outputs.release_tag }}
run: |
gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" > /dev/null
gh release upload "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --clobber \
arm64-dist/RetroArch-WinARM64.zip arm64-dist/RetroArch-WinARM64.zip.sha256
+30 -2
View File
@@ -22,15 +22,43 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-mxe-win32-cross:gcc10
image: reallibretroretroarch/libretro-build-mxe-win32-cross:gcc16
options: --user root
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 }}
+209
View File
@@ -0,0 +1,209 @@
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. Five
# 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; the raw driver as it
# was before any of this (RETROARCH_RAWINPUT_POLL=0); 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 }}
+239
View File
@@ -0,0 +1,239 @@
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
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
- 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 }}
+181
View File
@@ -0,0 +1,181 @@
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.*'
- '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.*'
- '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
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
./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
# 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: 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 }}
+30 -2
View File
@@ -22,15 +22,43 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
container:
image: reallibretroretroarch/libretro-build-mxe-win64-cross:gcc10
image: reallibretroretroarch/libretro-build-mxe-win64-cross:gcc16
options: --user root
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:
+89
View File
@@ -0,0 +1,89 @@
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]
pull_request:
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
+145
View File
@@ -0,0 +1,145 @@
name: CI procbarrier tiers
# retro_procbarrier picks one of several ways to force a memory barrier
# on every running thread, and which one depends on the OS and the CPU.
# The Linux samples job only ever runs the one its runner picks
# (membarrier). This runs every tier each platform has, on a multi-core
# runner so the fence checks can actually catch a missing barrier, and
# fails if a forced tier quietly fell back to another one.
#
# Linux x86_64 membarrier, page-flip, signal
# Linux arm64 membarrier, signal
# Windows x64 FlushProcessWriteBuffers, page-flip
# Windows arm64 FlushProcessWriteBuffers
# macOS arm64 signal
# macOS x86_64 page-flip, signal
on:
push:
branches: [master]
paths:
- 'libretro-common/rthreads/**'
- 'libretro-common/include/rthreads/**'
- 'libretro-common/include/retro_atomic.h'
- 'libretro-common/samples/rthreads/retro_procbarrier_test/**'
- 'libretro-common/samples/rthreads/retro_asym_eventcount_test/**'
- '.github/workflows/procbarrier-tiers.yml'
pull_request:
paths:
- 'libretro-common/rthreads/**'
- 'libretro-common/include/rthreads/**'
- 'libretro-common/include/retro_atomic.h'
- 'libretro-common/samples/rthreads/retro_procbarrier_test/**'
- 'libretro-common/samples/rthreads/retro_asym_eventcount_test/**'
- '.github/workflows/procbarrier-tiers.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:
posix:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- name: linux-x86_64
os: ubuntu-latest
libs: -lpthread -lrt
tiers: natural=membarrier membarrier=membarrier pageflip=page-flip signal=signal
- name: linux-arm64
os: ubuntu-24.04-arm
libs: -lpthread -lrt
tiers: natural=membarrier membarrier=membarrier signal=signal
- name: macos-arm64
os: macos-latest
libs: -lpthread
tiers: natural=signal signal=signal
- name: macos-x86_64
os: macos-15-intel
libs: -lpthread
tiers: natural=page-flip pageflip=page-flip signal=signal
steps:
- uses: actions/checkout@v5
- name: Build
working-directory: libretro-common
run: |
set -e
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 -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 \
-o retro_asym_eventcount_test ${{ matrix.libs }}
- name: Run every tier
working-directory: libretro-common
env:
REQUIRE_SMP: 1
run: |
bash samples/rthreads/retro_procbarrier_test/run_tiers.sh \
./retro_procbarrier_test ./retro_asym_eventcount_test \
${{ matrix.tiers }}
windows:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- name: windows-x64
os: windows-2022
vcvars: vcvars64.bat
tiers: natural=FlushProcessWriteBuffers fpwb=FlushProcessWriteBuffers pageflip=page-flip
- name: windows-arm64
os: windows-11-arm
vcvars: vcvarsarm64.bat
tiers: natural=FlushProcessWriteBuffers fpwb=FlushProcessWriteBuffers
steps:
- uses: actions/checkout@v5
- name: Build
shell: cmd
working-directory: libretro-common
run: |
for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -property installationPath`) do set VSPATH=%%i
if not defined VSPATH exit /b 1
call "%VSPATH%\VC\Auxiliary\Build\${{ matrix.vcvars }}"
cl /nologo /W3 /O1 /DHAVE_THREADS /Iinclude ^
samples\rthreads\retro_procbarrier_test\retro_procbarrier_test.c ^
rthreads\retro_procbarrier.c rthreads\rthreads.c ^
/Fe:retro_procbarrier_test.exe
if errorlevel 1 exit /b 1
cl /nologo /W3 /O1 /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 ^
/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
working-directory: libretro-common
env:
REQUIRE_SMP: 1
run: |
bash samples/rthreads/retro_procbarrier_test/run_tiers.sh \
./retro_procbarrier_test.exe ./retro_asym_eventcount_test.exe \
${{ matrix.tiers }}
+28 -22
View File
@@ -23,31 +23,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 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
timeout-minutes: 30
steps:
- name: Dependencies
timeout-minutes: 5
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
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
@@ -55,4 +61,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,17 @@
#!/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.
#
# 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)
prefix="apt-${ImageOS:-unknown}-$hash-"
key="$prefix${ImageVersion:-unknown}"
echo "key=$key" >> "$GITHUB_OUTPUT"
echo "prefix=$prefix" >> "$GITHUB_OUTPUT"
echo "apt cache key: $key"
+92
View File
@@ -0,0 +1,92 @@
#!/bin/sh
# Installs Ubuntu packages for a CI job, from the job's apt cache when it
# has them.
#
# apt-install.sh [--no-install-recommends] PACKAGE...
#
# APT_CACHE (default ~/.apt-cache) holds the package lists and the .deb
# files of the last install; actions/cache restores it before this runs
# and saves it after (see apt-cache-key.sh for the key). With it warm the
# packages install without the network: no apt-get update, no download,
# only the unpacking. Cold, or when the cached lists no longer resolve
# (a new runner image), the lists are refreshed and the packages fetched
# into it, under a bound, with the main archive tried when the runner's
# mirror stalls - and the cache is marked to be saved again.
#
# Packages already on the runner are left as they are, and when all of
# them are, nothing is done at all.
set -eu
opts=
while [ $# -gt 0 ]; do
case $1 in
--no-install-recommends) opts="$opts $1"; shift ;;
-*) echo "apt-install.sh: unknown option $1"; exit 2 ;;
*) break ;;
esac
done
[ $# -gt 0 ] || { echo "apt-install.sh: no packages"; exit 2; }
pkgs="$*"
cache=${APT_CACHE:-$HOME/.apt-cache}
if dpkg -s $pkgs >/dev/null 2>&1; then
echo "apt-install.sh: all present on the runner; nothing to install."
exit 0
fi
mkdir -p "$cache/archives/partial" "$cache/lists/partial"
dirs="-o Dir::Cache::Archives=$cache/archives -o Dir::State::Lists=$cache/lists"
net="-o Acquire::Retries=3 -o Acquire::http::Timeout=30"
# No package may stop the install to ask a question
apt="sudo env DEBIAN_FRONTEND=noninteractive apt-get"
saved() {
# A later plain apt-get install in the job resolves from the same
# lists, and the cache action runs as the runner's user
sudo cp -a "$cache/lists/." /var/lib/apt/lists/
sudo rm -f /var/lib/apt/lists/.complete
sudo chown -R "$(id -u):$(id -g)" "$cache"
}
# Warm: everything from the cache
if [ -f "$cache/lists/.complete" ] \
&& $apt install -y --no-download $dirs $opts $pkgs; then
echo "apt-install.sh: installed from the cache."
saved
exit 0
fi
echo "apt-install.sh: refreshing the cache."
rm -f "$cache/lists/.complete"
upd="$net -o Acquire::Languages=none
-o Acquire::IndexTargets::deb::CNF::DefaultEnabled=false"
for i in 1 2 3; do
sudo timeout 120 apt-get update -y $dirs $upd && break
[ $i = 3 ] && exit 1
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.
for i in 1 2 3; do
sudo timeout 240 apt-get install -y --download-only $dirs $net $opts $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 120 apt-get update -y $dirs $upd || true
done
$apt install -y $dirs $net $opts $pkgs
# Only what this job installs is kept: the lists, and its own packages
sudo touch "$cache/lists/.complete"
sudo rm -rf "$cache/archives/partial" "$cache/lists/partial"
saved
if [ -n "${GITHUB_ENV:-}" ]; then
echo "APT_CACHE_SAVE=1" >> "$GITHUB_ENV"
fi
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# Runs make with its C and C++ compilers wrapped in ccache, for the
# platform Makefiles that set CC and CXX themselves. The compilers are
# read from the same Makefile and variables, so the wrapped build is
# the build CI ran before.
# bash .github/workflows/scripts/ccache-make.sh -f Makefile.libnx -j4 all
set -euo pipefail
if ! command -v ccache > /dev/null; then
echo "ccache-make: no ccache, building without it"
exec make "$@"
fi
args=("$@")
# The options and variable assignments, without the targets.
opts=()
while [ $# -gt 0 ]; do
case $1 in
-f|-C|-I|-o|-W) opts+=("$1" "$2"); shift ;;
-*|*=*) opts+=("$1") ;;
esac
shift
done
print() {
make "${opts[@]}" --no-print-directory \
--eval "ra-print: ; @echo \$($1)" ra-print | tail -n 1
}
cc=$(print CC)
cxx=$(print CXX)
if [ -z "$cc" ] || [ -z "$cxx" ]; then
echo "ccache-make: could not read CC and CXX" >&2
exit 1
fi
echo "ccache-make: CC=$cc CXX=$cxx"
exec make "${args[@]}" CC="ccache $cc" CXX="ccache $cxx"
File diff suppressed because it is too large. Load diff
@@ -1,5 +1,6 @@
# function size stack-frame (release build, x86-64 GCC)
runloop_iterate 3933 88
# Driver Pacing release/marker dispatch adds code; the stack guard stays put.
runloop_iterate 6527 88
video_driver_frame 9447 4008
video_thread_frame 3097 152
video_thread_frame 3873 152
audio_driver_sample_batch 394 24
+67
View File
@@ -0,0 +1,67 @@
#!/bin/sh
# The iOS 6 build, compiled for armv7 the way RetroArch_iOS6.xcodeproj
# builds it: its own defines (read from the project, so this follows
# it), ARC, gnu99, against an iOS SDK with a 6.0 deployment target.
#
# Everything newer than iOS 6 has to sit behind a runtime check -
# apple_runtime_available() and a selector send, or an availability-
# annotated method - and clang's -Wunguarded-availability is what
# enforces that, so every warning is an error here. An API the SDK does
# not declare at all is an error too: the iOS 6 build reaches newer
# APIs by selector, not by needing a newer SDK.
#
# The SDK is the iOS 9.3 one - Xcode 7's, the last to take a 6.0
# deployment target - and only its headers are needed: this compiles,
# it does not link.
#
# usage: ios-legacy-check.sh <path to iPhoneOS9.3.sdk> [floor]
# floor defaults to 6.0
#
# Needs clang (any version that targets armv7-apple-ios).
SDK=$1
FLOOR=${2:-6.0}
CC=${CC:-clang}
PROJECT=pkg/apple/RetroArch_iOS6.xcodeproj/project.pbxproj
if [ -z "$SDK" ] || [ ! -d "$SDK/System/Library/Frameworks/UIKit.framework" ]; then
echo "usage: $0 <path to iPhoneOS9.3.sdk> [floor]" >&2
exit 2
fi
if [ ! -f "$PROJECT" ]; then
echo "run from the top of the RetroArch tree" >&2
exit 2
fi
# The project's OTHER_CFLAGS defines, each once
DEFS=$(grep -o '"-D[A-Za-z_0-9=]*"' "$PROJECT" | tr -d '"' | sort -u | tr '\n' ' ')
# Xcode finds the project's own headers by bare name through its
# header map; every directory under pkg/apple and ui/drivers that
# holds one stands in for it.
HMAP=$(find pkg/apple ui/drivers -name '*.h' -exec dirname {} \; | sort -u | sed 's/^/-I/' | tr '\n' ' ')
INC="-Ideps -Ideps/7zip -Ideps/rcheevos/include -Ideps/stb -I. -Igfx/include -Ilibretro-common/include"
FLAGS="-fsyntax-only -target armv7-apple-ios$FLOOR -isysroot $SDK -std=gnu99 -fblocks
-Wunguarded-availability -Wunguarded-availability-new -Wdeprecated-declarations
-Wno-nullability-completeness -Werror -ferror-limit=0"
fail=0
check() {
name=$1; lang=$2; tu=$3; shift 3
# shellcheck disable=SC2086
if out=$($CC -x "$lang" $FLAGS "$@" $DEFS $INC $HMAP "$tu" 2>&1); then
echo "ok $name"
else
echo "FAIL $name"
printf '%s\n' "$out" | sed 's/^/ /'
fail=1
fi
}
echo "== iOS $FLOOR, armv7, $(basename "$SDK") =="
check "griffin_objc.m (ARC)" objective-c griffin/griffin_objc.m -fobjc-arc
check "griffin.c" c griffin/griffin.c
exit $fail
@@ -1,4 +0,0 @@
#ifndef STUB_3DS_H
#define STUB_3DS_H
#include <stdint.h>
#endif
@@ -1,7 +0,0 @@
#ifndef STUB_3DS_OS_H
#define STUB_3DS_OS_H
#include <stdint.h>
typedef enum { MEMREGION_ALL = 0 } MemRegion;
uint32_t osGetMemRegionSize(MemRegion region);
uint32_t osGetMemRegionFree(MemRegion region);
#endif
-372
View File
@@ -1,372 +0,0 @@
/* Stand-in for devkitPro's libogc <gccore.h>, for the GX video driver.
*
* Prototypes and values follow libogc's, so the driver's calls are
* checked as it makes them. As the note at the top of compile-matrix.sh
* says, this does not prove the real SDK matches: it proves RetroArch's
* own GX code is well formed and uses only what it has arranged to
* have. Pinning the surface here rather than fetching the SDK is
* deliberate - the 3DS driver no longer compiles against a current
* libctru, whose gspSubmitGxCommand() changed shape under it, and a
* lane that goes red when somebody else's headers move tells us
* nothing about this tree.
*/
#ifndef LIBOGC_STUB_GCCORE_H
#define LIBOGC_STUB_GCCORE_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t s8;
typedef int16_t s16;
typedef int32_t s32;
typedef int64_t s64;
typedef float f32;
typedef double f64;
typedef volatile uint8_t vu8;
typedef volatile uint16_t vu16;
typedef volatile uint32_t vu32;
typedef uint32_t lwpq_t;
typedef uint32_t lwp_t;
typedef uint32_t mutex_t;
typedef uint32_t syswd_t;
typedef struct { u32 dummy[16]; } GXFifoObj;
typedef void (*VIRetraceCallback)(u32 retraceCnt);
typedef void (*VIPositionCallback)(s16 x, s16 y);
typedef f32 Mtx[3][4];
typedef f32 Mtx44[4][4];
#define ATTRIBUTE_ALIGN(v) __attribute__((aligned(v)))
#define ATTRIBUTE_PACKED __attribute__((packed))
#define LWP_TQUEUE_NULL 0xffffffff
#define SYS_BASE_CACHED 0x80000000
#define SYS_BASE_UNCACHED 0xc0000000
/* uintptr_t, not u32: on the console a pointer is 32 bits and libogc's
* own cast is exact, while the host running this check is 64-bit and
* would lose half of one. The arithmetic is what matters here. */
#define MEM_K0_TO_K1(x) ((void*)((uintptr_t)(x) + (SYS_BASE_UNCACHED - SYS_BASE_CACHED)))
#define MEM_K1_TO_K0(x) ((void*)((uintptr_t)(x) - (SYS_BASE_UNCACHED - SYS_BASE_CACHED)))
#define VIDEO_PadFramebufferWidth(w) ((u16)(((u16)(w) + 15) & ~15))
#define VI_TVMODE(fmt, mode) (((fmt) << 2) + (mode))
typedef struct { u8 r, g, b, a; } GXColor;
typedef struct { u32 dummy[8]; } GXTexObj;
typedef struct
{
u32 viTVMode;
u16 fbWidth, efbHeight, xfbHeight;
u16 viXOrigin, viYOrigin, viWidth, viHeight;
u32 xfbMode;
u8 field_rendering, aa;
u8 sample_pattern[12][2];
u8 vfilter[7];
} GXRModeObj;
typedef enum { CONF_VIDEO_NTSC = 0, CONF_VIDEO_PAL, CONF_VIDEO_MPAL } CONFVideoMode;
#ifndef COLOR_BLACK
#define COLOR_BLACK (0x10801080)
#endif
#ifndef VI_DISPLAY_PIX_SZ
#define VI_DISPLAY_PIX_SZ 2
#endif
#ifndef VI_EURGB60
#define VI_EURGB60 5
#endif
#ifndef VI_INTERLACE
#define VI_INTERLACE 0
#endif
#ifndef VI_MAX_HEIGHT_EURGB60
#define VI_MAX_HEIGHT_EURGB60 VI_MAX_HEIGHT_NTSC
#endif
#ifndef VI_MAX_HEIGHT_MPAL
#define VI_MAX_HEIGHT_MPAL 480
#endif
#ifndef VI_MAX_HEIGHT_NTSC
#define VI_MAX_HEIGHT_NTSC 480
#endif
#ifndef VI_MAX_HEIGHT_PAL
#define VI_MAX_HEIGHT_PAL 576
#endif
#ifndef VI_MAX_WIDTH_EURGB60
#define VI_MAX_WIDTH_EURGB60 VI_MAX_WIDTH_NTSC
#endif
#ifndef VI_MAX_WIDTH_MPAL
#define VI_MAX_WIDTH_MPAL 720
#endif
#ifndef VI_MAX_WIDTH_NTSC
#define VI_MAX_WIDTH_NTSC 720
#endif
#ifndef VI_MAX_WIDTH_PAL
#define VI_MAX_WIDTH_PAL 720
#endif
#ifndef VI_MPAL
#define VI_MPAL 2
#endif
#ifndef VI_NON_INTERLACE
#define VI_NON_INTERLACE 1
#endif
#ifndef VI_NTSC
#define VI_NTSC 0
#endif
#ifndef VI_PAL
#define VI_PAL 1
#endif
#ifndef VI_PROGRESSIVE
#define VI_PROGRESSIVE 2
#endif
#ifndef VI_XFBMODE_DF
#define VI_XFBMODE_DF 1
#endif
#ifndef VI_XFBMODE_SF
#define VI_XFBMODE_SF 0
#endif
typedef struct { u32 dummy[8]; } syssram;
/* Drawing primitives libogc gives as inline functions */
static inline void GX_Position3f32(f32 x, f32 y, f32 z) { (void)x; (void)y; (void)z; }
static inline void GX_Position1x8(u8 i) { (void)i; }
static inline void GX_Color4u8(u8 r, u8 g, u8 b, u8 a) { (void)r; (void)g; (void)b; (void)a; }
static inline void GX_Color1x8(u8 i) { (void)i; }
static inline void GX_TexCoord2f32(f32 s, f32 t) { (void)s; (void)t; }
static inline void GX_TexCoord1x8(u8 i) { (void)i; }
static inline void GX_End(void) { }
void VIDEO_SetGamma(int gamma);
/* The matrix helpers the driver sets its projection up with */
void guOrtho(Mtx44 mt, f32 t, f32 b, f32 l, f32 r, f32 n, f32 f);
void guMtxIdentity(Mtx mt);
void c_guMtxConcat(Mtx a, Mtx b, Mtx ab);
void DCFlushRange(void *startaddress, u32 len);
void DCInvalidateRange(void *startaddress, u32 len);
void guMtxRotDeg(Mtx mt, const char axis, f32 deg);
/* The threading calls libretro-common's gx_defines.h maps OS* onto */
void LWP_InitQueue(lwpq_t *queue);
void LWP_CloseQueue(lwpq_t queue);
void LWP_ThreadSignal(lwpq_t queue);
void LWP_ThreadBroadcast(lwpq_t queue);
s32 LWP_ThreadSleep(lwpq_t queue);
s32 LWP_MutexInit(mutex_t *mutex, bool use_recursive);
s32 LWP_MutexLock(mutex_t mutex);
s32 LWP_MutexTryLock(mutex_t mutex);
s32 LWP_MutexUnlock(mutex_t mutex);
s32 LWP_MutexDestroy(mutex_t mutex);
s32 LWP_CondInit(lwpq_t *cond);
s32 LWP_CondWait(lwpq_t cond, mutex_t mutex);
s32 LWP_CondDestroy(lwpq_t cond);
s32 LWP_CreateThread(lwp_t *thethread, void *(*entry)(void *),
void *arg, void *stackbase, u32 stack_size, u8 prio);
s32 LWP_JoinThread(lwp_t thethread, void **value_ptr);
void VIDEO_SetTrapFilter(bool enable);
#ifndef CONF_ASPECT_4_3
#define CONF_ASPECT_4_3 0
#endif
#ifndef GX_AF_NONE
#define GX_AF_NONE 2
#endif
#ifndef GX_ALWAYS
#define GX_ALWAYS 7
#endif
#ifndef GX_BL_INVSRCALPHA
#define GX_BL_INVSRCALPHA 5
#endif
#ifndef GX_BL_SRCALPHA
#define GX_BL_SRCALPHA 4
#endif
#ifndef GX_BM_BLEND
#define GX_BM_BLEND 1
#endif
#ifndef GX_CLAMP
#define GX_CLAMP 0
#endif
#ifndef GX_CLIP_DISABLE
#define GX_CLIP_DISABLE 1
#endif
#ifndef GX_CLR_RGBA
#define GX_CLR_RGBA 1
#endif
#ifndef GX_COLOR0A0
#define GX_COLOR0A0 4
#endif
#ifndef GX_CULL_NONE
#define GX_CULL_NONE 0
#endif
#ifndef GX_DF_NONE
#define GX_DF_NONE 0
#endif
#ifndef GX_DIRECT
#define GX_DIRECT 1
#endif
#ifndef GX_DISABLE
#define GX_DISABLE 0
#endif
#ifndef GX_ENABLE
#define GX_ENABLE 1
#endif
#ifndef GX_F32
#define GX_F32 4
#endif
#ifndef GX_FALSE
#define GX_FALSE 0
#endif
#ifndef GX_INDEX8
#define GX_INDEX8 2
#endif
#ifndef GX_LIGHTNULL
#define GX_LIGHTNULL 0x000
#endif
#ifndef GX_LINEAR
#define GX_LINEAR 1
#endif
#ifndef GX_LO_CLEAR
#define GX_LO_CLEAR 0
#endif
#ifndef GX_MAX_Z24
#define GX_MAX_Z24 0x00ffffff
#endif
#ifndef GX_MODULATE
#define GX_MODULATE 0
#endif
#ifndef GX_NEAR
#define GX_NEAR 0
#endif
#ifndef GX_ORTHOGRAPHIC
#define GX_ORTHOGRAPHIC 1
#endif
#ifndef GX_PF_RGB8_Z24
#define GX_PF_RGB8_Z24 0
#endif
#ifndef GX_PNMTX0
#define GX_PNMTX0 0
#endif
#ifndef GX_PNMTX1
#define GX_PNMTX1 3
#endif
#ifndef GX_POS_XYZ
#define GX_POS_XYZ 1
#endif
#ifndef GX_RGBA8
#define GX_RGBA8 5
#endif
#ifndef GX_SRC_REG
#define GX_SRC_REG 0
#endif
#ifndef GX_SRC_VTX
#define GX_SRC_VTX 1
#endif
#ifndef GX_TEVSTAGE0
#define GX_TEVSTAGE0 0
#endif
#ifndef GX_TEXCOORD0
#define GX_TEXCOORD0 0x0
#endif
#ifndef GX_TEXMAP0
#define GX_TEXMAP0 0
#endif
#ifndef GX_TEX_ST
#define GX_TEX_ST 1
#endif
#ifndef GX_TF_RGB565
#define GX_TF_RGB565 0x4
#endif
#ifndef GX_TF_RGB5A3
#define GX_TF_RGB5A3 0x5
#endif
#ifndef GX_TF_RGBA8
#define GX_TF_RGBA8 0x6
#endif
#ifndef GX_TRIANGLESTRIP
#define GX_TRIANGLESTRIP 0x98
#endif
#ifndef GX_TRUE
#define GX_TRUE 1
#endif
#ifndef GX_VA_CLR0
#define GX_VA_CLR0 11
#endif
#ifndef GX_VA_POS
#define GX_VA_POS 9
#endif
#ifndef GX_VA_TEX0
#define GX_VA_TEX0 13
#endif
#ifndef GX_VTXFMT0
#define GX_VTXFMT0 0
#endif
#ifndef GX_ZC_LINEAR
#define GX_ZC_LINEAR 0
#endif
#ifndef LWP_TQUEUE_NULL
#define LWP_TQUEUE_NULL 0xffffffff
#endif
s32 CONF_GetAspectRatio(void);
s32 CONF_GetDisplayOffsetH(s8 *offset);
s32 CONF_GetEuRGB60(void);
s32 CONF_GetProgressiveScan(void);
s32 CONF_GetVideo(void);
void GX_AbortFrame(void);
void GX_Begin(u8 primitve,u8 vtxfmt,u16 vtxcnt);
void GX_BeginDispList(void *list,u32 size);
void GX_CallDispList(void *list,u32 nbytes);
void GX_ClearVtxDesc(void);
void GX_CopyDisp(void *dest,u8 clear);
void GX_DrawDone(void);
u32 GX_EndDispList(void);
void GX_Flush(void);
f32 GX_GetYScaleFactor(u16 efbHeight,u16 xfbHeight);
GXFifoObj* GX_Init(void *base,u32 size);
void GX_InitTexObj(GXTexObj *obj,void *img_ptr,u16 wd,u16 ht,u8 fmt,u8 wrap_s,u8 wrap_t,u8 mipmap);
void GX_InitTexObjFilterMode(GXTexObj *obj,u8 minfilt,u8 magfilt);
void GX_InvVtxCache(void);
void GX_InvalidateTexAll(void);
void GX_LoadPosMtxImm(Mtx mt,u32 pnidx);
void GX_LoadProjectionMtx(Mtx44 mt,u8 type);
void GX_LoadTexObj(GXTexObj *obj,u8 mapid);
void GX_PeekARGB(u16 x,u16 y,GXColor *color);
void GX_PokeARGB(u16 x,u16 y,GXColor color);
void GX_SetAlphaUpdate(u8 enable);
void GX_SetArray(u32 attr,void *ptr,u8 stride);
void GX_SetBlendMode(u8 type,u8 src_fact,u8 dst_fact,u8 op);
void GX_SetChanCtrl(s32 channel,u8 enable,u8 ambsrc,u8 matsrc,u8 litmask,u8 diff_fn,u8 attn_fn);
void GX_SetClipMode(u8 mode);
void GX_SetColorUpdate(u8 enable);
void GX_SetCopyClear(GXColor color,u32 zvalue);
void GX_SetCopyFilter(u8 aa,u8 sample_pattern[12][2],u8 vf,u8 vfilter[7]);
void GX_SetCullMode(u8 mode);
void GX_SetCurrentMtx(u32 mtx);
void GX_SetDispCopyDst(u16 wd,u16 ht);
void GX_SetDispCopyGamma(u8 gamma);
void GX_SetDispCopySrc(u16 left,u16 top,u16 wd,u16 ht);
u32 GX_SetDispCopyYScale(f32 yscale);
void GX_SetFieldMode(u8 field_mode,u8 half_aspect_ratio);
void GX_SetNumChans(u8 num);
void GX_SetNumTexGens(u32 nr);
void GX_SetPixelFmt(u8 pix_fmt,u8 z_fmt);
void GX_SetTevOp(u8 tevstage,u8 mode);
void GX_SetTevOrder(u8 tevstage,u8 texcoord,u32 texmap,u8 color);
void GX_SetViewportJitter(f32 xOrig,f32 yOrig,f32 wd,f32 ht,f32 nearZ,f32 farZ,u32 field);
void GX_SetVtxAttrFmt(u8 vtxfmt,u32 vtxattr,u32 comptype,u32 compsize,u32 frac);
void GX_SetVtxDesc(u8 attr,u8 type);
void GX_SetZMode(u8 enable,u8 func,u8 update_enable);
u32 SYS_GetArena1Size(void);
void VIDEO_ClearFrameBuffer(GXRModeObj *rmode,void *fb,u32 color);
void VIDEO_Configure(GXRModeObj *rmode);
void VIDEO_Flush(void);
u32 VIDEO_GetCurrentTvMode(void);
u32 VIDEO_GetNextField(void);
GXRModeObj * VIDEO_GetPreferredMode(GXRModeObj *mode);
u32 VIDEO_HaveComponentCable(void);
void VIDEO_Init(void);
void VIDEO_SetBlack(bool black);
void VIDEO_SetNextFramebuffer(void *fb);
VIRetraceCallback VIDEO_SetPostRetraceCallback(VIRetraceCallback callback);
void VIDEO_WaitVSync(void);
#endif
@@ -1,54 +0,0 @@
/* Stand-in for devkitPro's libnx <switch.h>.
*
* Only what the translation units checked here name, declared with the
* shapes libnx gives them. As the note at the top of compile-matrix.sh
* says, this does not prove the real SDK matches: it proves RetroArch's
* own Switch code is well formed and that every symbol it uses is one it
* has arranged to have. That is the class of breakage these lanes are
* for - a driver naming a struct member or a setting that moved.
*/
#ifndef LIBNX_STUB_SWITCH_H
#define LIBNX_STUB_SWITCH_H
#include <stdint.h>
#include <stddef.h>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t s32;
typedef uint32_t Result;
/* mem_stats.c */
typedef enum { InfoType_TotalMemorySize = 6, InfoType_UsedMemorySize = 7 } InfoType;
#define CUR_PROCESS_HANDLE 0xFFFF8001
#define R_SUCCEEDED(res) ((res) == 0)
#define R_FAILED(res) ((res) != 0)
Result svcGetInfo(u64 *out, u32 id, u32 handle, u64 sub);
/* The window and framebuffer the Switch video driver draws through */
typedef struct NWindow NWindow;
typedef struct
{
void *buf;
u32 stride;
} Framebuffer;
typedef enum
{
PIXEL_FORMAT_RGBA_8888 = 1
} PixelFormat;
NWindow *nwindowGetDefault(void);
Result nwindowSetDimensions(NWindow *nw, u32 width, u32 height);
Result framebufferCreate(Framebuffer *fb, NWindow *win,
u32 width, u32 height, u32 format, u32 num_fbs);
Result framebufferMakeLinear(Framebuffer *fb);
void *framebufferBegin(Framebuffer *fb, u32 *out_stride);
void framebufferEnd(Framebuffer *fb);
void framebufferClose(Framebuffer *fb);
#endif
@@ -1,5 +0,0 @@
#ifndef STUB_PPU_LV2_H
#define STUB_PPU_LV2_H
int lv2syscall1_impl(int n, unsigned long a);
#define lv2syscall1(n, a) lv2syscall1_impl((n), (unsigned long)(a))
#endif
+59
View File
@@ -0,0 +1,59 @@
#!/bin/sh
# Makes the host gcc link with LLVM's lld for the rest of the job.
#
# lld comes with the runner image, but under a versioned name
# (ld.lld-18 and so on) that changes as the image does, so it is looked
# up rather than named. A small "ld" that runs it is put first on PATH:
# the host gcc runs the first "ld" on PATH, while cross compilers run
# their own prefixed linker (x86_64-w64-mingw32-ld, ...) and clang its
# own, so neither of those changes.
#
# GNU ld still does two kinds of link:
# --wrap lld also wraps the calls made inside the object that
# defines the symbol, GNU ld does not; the samples that wrap
# are written for GNU ld's rule.
# gcc -flto objects, which lld cannot read: lld fails on them, and the
# link is run again with GNU ld.
# No lld found leaves the job on GNU ld; that is never an error.
set -eu
lld=
if command -v ld.lld >/dev/null 2>&1; then
lld=$(command -v ld.lld)
else
for c in $(ls /usr/bin/ld.lld-* /usr/lib/llvm-*/bin/ld.lld 2>/dev/null \
| sort -V -r); do
if [ -x "$c" ]; then
lld=$c
break
fi
done
fi
gnu=$(command -v ld.bfd || command -v ld || true)
if [ -z "$lld" ] || [ -z "$gnu" ]; then
echo "use-lld: no lld on this runner; linking with the default ld"
exit 0
fi
dir="${RUNNER_TEMP:-/tmp}/lld-bin"
mkdir -p "$dir"
cat > "$dir/ld" <<WRAPPER
#!/bin/sh
for a; do
case "\$a" in --wrap*|-wrap*) exec "$gnu" "\$@" ;; esac
done
err=\$(mktemp)
if "$lld" "\$@" 2>"\$err"; then
cat "\$err" >&2
rm -f "\$err"
exit 0
fi
rm -f "\$err"
exec "$gnu" "\$@"
WRAPPER
chmod +x "$dir/ld"
echo "use-lld: $lld ($("$lld" --version | head -n 1)), GNU ld $gnu"
if [ -n "${GITHUB_PATH:-}" ]; then
echo "$dir" >> "$GITHUB_PATH"
fi
+48 -3
View File
@@ -25,6 +25,9 @@ jobs:
- name: Check Out Repo
uses: actions/checkout@v7
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Download ares-cli-rs
uses: robinraju/release-downloader@v1
with:
@@ -117,13 +120,41 @@ jobs:
curl -fsSL --retry 5 --retry-all-errors --retry-delay 5 \
--connect-timeout 30 -o "/tmp/sdl2-prebuilt/$SDL2_NAME" "$SDL2_URL"
- 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 (GLES2, no wayland legacy variant)
shell: bash
run: |
. /tmp/arm-webos-linux-gnueabi_sdk-buildroot/environment-setup
export SDK_PATH=/tmp/arm-webos-linux-gnueabi_sdk-buildroot
make -f Makefile.webos clean
make -f Makefile.webos ipk PACKAGE_NAME=${PACKAGE_NAME} ADD_SDL2_LIB=1 \
bash .github/workflows/scripts/ccache-make.sh -f Makefile.webos ipk PACKAGE_NAME=${PACKAGE_NAME} ADD_SDL2_LIB=1 \
SDL2_PREBUILT_ARCHIVE="file:///tmp/sdl2-prebuilt/${SDL2_NAME}" \
-j"$(getconf _NPROCESSORS_ONLN)"
mkdir -p webos/arm
@@ -147,7 +178,7 @@ jobs:
export SDK_PATH=/tmp/arm-webos-linux-gnueabi_sdk-buildroot
make -f Makefile.webos clean
bash gfx/common/wayland/generate_wayland_protos.sh
make -f Makefile.webos ipk PACKAGE_NAME=${PACKAGE_NAME} ADD_SDL2_LIB=1 \
bash .github/workflows/scripts/ccache-make.sh -f Makefile.webos ipk PACKAGE_NAME=${PACKAGE_NAME} ADD_SDL2_LIB=1 \
SDL2_PREBUILT_ARCHIVE="file:///tmp/sdl2-prebuilt/${SDL2_NAME}" \
HAVE_XKBCOMMON=1 HAVE_USERLAND=1 HAVE_EGL=1 HAVE_WAYLAND=1 \
HAVE_OPENGLES3=1 HAVE_OPENGLES3_1=1 HAVE_OPENGLES3_2=1 -j"$(getconf _NPROCESSORS_ONLN)"
@@ -181,7 +212,7 @@ jobs:
export SDK_PATH=/tmp/aarch64-webos-linux-gnu_sdk-buildroot
make -f Makefile.webos clean
bash gfx/common/wayland/generate_wayland_protos.sh
make -f Makefile.webos ipk PACKAGE_NAME=${PACKAGE_NAME} \
bash .github/workflows/scripts/ccache-make.sh -f Makefile.webos ipk PACKAGE_NAME=${PACKAGE_NAME} \
ADD_SDL2_LIB=0 HAVE_SDL2=0 HAVE_SDL3=1 \
HAVE_XKBCOMMON=1 HAVE_USERLAND=1 HAVE_EGL=1 HAVE_WAYLAND=1 \
HAVE_OPENGLES3=1 HAVE_OPENGLES3_1=1 HAVE_OPENGLES3_2=1 \
@@ -193,6 +224,20 @@ jobs:
env:
DEBUG: ${{ github.event_name == 'release' && '0' || '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: Upload aarch64 artifact
uses: actions/upload-artifact@v7
with:
+63 -2
View File
@@ -16,6 +16,7 @@
*.swp
*.cache
*.gcda
gmon.out
# clang --analyze drops a .plist report per translation unit into the
# working directory. Anchored to the repository root so the tracked
# Info.plist / ExportOptions.plist files under pkg/apple are unaffected.
@@ -86,7 +87,7 @@ apple/RetroArch_iOS.xcodeproj/project.xcworkspace/*
.cproject
.settings
libretro-super
run.sh
/run.sh
convert_rumble.awk
*~
assets
@@ -104,7 +105,7 @@ autoconfig
overlays
playlists
states
cheats
/cheats
thumbnails
docs/html
system
@@ -266,21 +267,69 @@ gfx/common/wayland/webos-surface-group.c
gfx/common/wayland/webos-surface-group.h
gfx/common/wayland/tearing-control-v1.h
gfx/common/wayland/tearing-control-v1.c
gfx/common/wayland/drm-lease-v1.h
gfx/common/wayland/drm-lease-v1.c
gfx/common/wayland/color-management-v1.c
gfx/common/wayland/color-management-v1.h
gfx/common/wayland/kde-output-device-v2.c
gfx/common/wayland/kde-output-device-v2.h
gfx/common/wayland/kde-output-management-v2.c
gfx/common/wayland/kde-output-management-v2.h
gfx/common/wayland/wlr-output-management-unstable-v1.c
gfx/common/wayland/wlr-output-management-unstable-v1.h
# samples/: built harness executables and objects. Each directory's
# test is <dir>_test; nothing built there is source.
samples/**/*_test
samples/**/*_test_*
samples/audio/stretch/stretch_bench
samples/gfx/rslc_corpus/rslc_corpus_dump
samples/gfx/rslc_reflect/rslc_reflect_check
samples/gfx/rslc_reflect/spirv_cross.o
samples/gfx/slang_blob_reflect/slang_blob_reflect_check
samples/gfx/rslc_image/pattern_core.so
samples/gfx/rslc_image/pattern_core.dll
samples/gfx/rslc_image/out/
samples/**/*.o
libretro-common/samples/**/*_test
libretro-common/samples/**/*_test_*
!libretro-common/samples/**/*_test/
!samples/**/*_test_*.c
!libretro-common/samples/**/*_test_*.c
!samples/**/*_test_*.h
!libretro-common/samples/**/*_test_*.h
# Harness executables that are not named <dir>_test. This one had been
# committed as a built binary, which made any make clean in that
# directory show up as a deletion.
samples/audio/embedded_alsa/abi_check
libretro-common/samples/rthreads/retro_eventcount_bench/retro_eventcount_bench
libretro-common/samples/rthreads/retro_eventcount_bench/retro_eventcount_bench_*
# The driver's text with its Windows includes swapped, produced by each
# suite's Makefile from audio/drivers/wasapi.c. Committed copies went
# stale against the driver they are generated from.
samples/audio/wasapi_exclusive_pacing/wasapi_sim.c
samples/audio/discrete_multichannel/wasapi_sim.c
samples/audio/wasapi_mic/wasapi_mic_sim.c
samples/gfx/font_lifecycle/lifecycle
samples/gfx/font_lifecycle/lifecycle-threaded
samples/gfx/font_lifecycle/create
samples/gfx/font_lifecycle/fallback
samples/gfx/font_lifecycle/chain
samples/gfx/font_lifecycle/frame
samples/gfx/font_lifecycle/vg
samples/gfx/font_lifecycle/sdl2
samples/gfx/slang_cache/mapped
samples/gfx/slang_cache/read
samples/tasks/database/database_task
samples/gfx/raster_tear_live/raster_tear_live
# The fake resizable-BAR layer's manifest, written by check-vulkan-rebar
# with the library name of the platform it is built on.
samples/gfx/threaded_video/fake_rebar_layer.json
# Generated by samples/gfx/wayland_drm_lease from the bundled protocol
# XML; the client half of the same pair lands in gfx/common/wayland.
samples/gfx/wayland_drm_lease/drm-lease-v1-client-protocol.h
samples/gfx/wayland_drm_lease/drm-lease-v1-server-protocol.h
samples/gfx/wayland_drm_lease/drm-lease-v1-protocol.c
# libretro-common samples
libretro-common/samples/streams/rzip/rzip
@@ -319,3 +368,15 @@ gfx/drivers/rsx_shaders/modern_opaque.vpo
pkg/psl1ght/pkg/USRDIR/shaders_cg/
!pkg/psl1ght/pkg/USRDIR/system/
samples/audio/embedded_alsa/ealsa_abi.h
# Python bytecode caches, written by tools/*.py whenever they run.
__pycache__/
*.pyc
samples/audio/asio_clock/asio_clock_driver.h
samples/gfx/integer_scale_axis/integer_scale_driver.h
samples/gfx/coord_array/coord_array_driver.h
samples/gfx/integer_scale_axis/integer_scale_axis_test
# wiiu/slang host tool
/wiiu/slang/slang-cafe
/wiiu/slang/slang-cafe.exe
+9 -6
View File
@@ -1,5 +1,8 @@
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-amd64-ubuntu:latest
default:
interruptible: true
stages:
- build-prepare
- build
@@ -7,7 +10,7 @@ stages:
- trigger-static-cores
build-retroarch-windows-x64:
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-mxe-win64-cross:gcc10
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-mxe-win64-cross:gcc16
stage: build
variables:
MEDIA_PATH: .media
@@ -51,7 +54,7 @@ build-retroarch-windows-x64:
- "rm -f ${MEDIA_PATH}/${CI_PROJECT_NAME}/redist/opengl32.dll"
build-retroarch-windows-i686:
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-mxe-win32-cross:gcc10
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-mxe-win32-cross:gcc16
stage: build
variables:
MEDIA_PATH: .media
@@ -358,7 +361,7 @@ build-retroarch-linux-aarch64:
# Non-Qt build
- "mkdir -p ${MEDIA_PATH}/${CI_PROJECT_NAME}/AppDir"
- "make clean"
- "./configure --disable-ffmpeg --disable-qt --prefix=/usr"
- "./configure --disable-ffmpeg --disable-qt --enable-openxr --prefix=/usr"
- "make -j$NUMPROC"
- if [ $STRIP_BIN -eq 1 ]; then strip --strip-unneeded retroarch; fi
- "make install DESTDIR=${MEDIA_PATH}/${CI_PROJECT_NAME}/AppDir prefix=/usr"
@@ -1078,7 +1081,7 @@ build-static-retroarch-wii:
- build-static-retroarch-dummy-wii
script:
# Allow failure since we don't have a core
- "make -f Makefile.wii -j$NUMPROC EXTERNAL_LIBOGC=1 GX_PTHREAD_LEGACY=0 ||:"
- "make -f Makefile.wii -j$NUMPROC HAVE_LIBOGC=1 ||:"
- "mkdir .retroarch-precompiled"
- "cp -r ./* .retroarch-precompiled/"
- "mv .retroarch-precompiled/ retroarch-precompiled/"
@@ -1097,7 +1100,7 @@ build-static-retroarch-dummy-wii:
expire_in: 10 min
dependencies: []
script:
- "make -f Makefile.wii.salamander -j$NUMPROC EXTERNAL_LIBOGC=1 GX_PTHREAD_LEGACY=0"
- "make -f Makefile.wii.salamander -j$NUMPROC HAVE_LIBOGC=1"
- "mv -f retroarch-salamander_wii.dol boot.dol"
- "mkdir -p ${MEDIA_PATH}/${CI_PROJECT_NAME}/filters/audio"
- "mkdir -p ${MEDIA_PATH}/${CI_PROJECT_NAME}/filters/video"
@@ -1122,7 +1125,7 @@ build-static-retroarch-ngc:
- build-static-retroarch-dummy-ngc
script:
# Allow failure since we don't have a core
- "make -f Makefile.ngc -j$NUMPROC EXTERNAL_LIBOGC=1 GX_PTHREAD_LEGACY=0 ||:"
- "make -f Makefile.ngc -j$NUMPROC HAVE_LIBOGC=1 ||:"
- "mkdir .retroarch-precompiled"
- "cp -r ./* .retroarch-precompiled/"
- "mv .retroarch-precompiled/ retroarch-precompiled/"
-185
View File
@@ -1,185 +0,0 @@
# This setting is to prevent travis to launch duplicate builds on PRs
branches:
only:
- master
- /^v.*$/
# Use 'generic' to be able to override CC/CXX for clang
language: generic
# Use a release with a longer normal LTS
dist: xenial
matrix:
include:
- compiler: mingw-x86
addons: true
env: CROSS_COMPILE=i686-w64-mingw32- CFLAGS="-D_WIN32_WINNT=0x0501"
- compiler: mingw-x86
addons: true
env: C89_BUILD=1 CROSS_COMPILE=i686-w64-mingw32- CFLAGS="-D_WIN32_WINNT=0x0501"
- compiler: mingw-x86
addons: true
env: CXX_BUILD=1 CROSS_COMPILE=i686-w64-mingw32- CFLAGS="-D_WIN32_WINNT=0x0501"
- compiler: mingw-x64
addons: true
env: CROSS_COMPILE=x86_64-w64-mingw32- CFLAGS="-D_WIN32_WINNT=0x0501"
- compiler: mingw-x64
addons: true
env: C89_BUILD=1 CROSS_COMPILE=x86_64-w64-mingw32- CFLAGS="-D_WIN32_WINNT=0x0501"
- compiler: mingw-x64
addons: true
env: CXX_BUILD=1 CROSS_COMPILE=x86_64-w64-mingw32- CFLAGS="-D_WIN32_WINNT=0x0501"
- compiler: gcc
env: CC=gcc-8 CXX=g++-8
- compiler: gcc
env: CC="gcc-8 --sysroot=/" CXX="g++-8 --sysroot=/"
- compiler: gcc
env: C89_BUILD=1 CC=gcc-8 CXX=g++-8
- compiler: gcc
env: CXX_BUILD=1 CC=gcc-8 CXX=g++-8
- compiler: gcc
env: DISABLE_MENU=1 CC=gcc-8 CXX=g++-8
- compiler: gcc
env: DISABLE_NETWORKING=1 CC=gcc-8 CXX=g++-8
- compiler: gcc
env: DISABLE_OVERLAY=1 CC=gcc-8 CXX=g++-8
- compiler: gcc
env: DISABLE_CDROM=1 CC=gcc-8 CXX=g++-8
- compiler: gcc
env: DISABLE_VIDEO_LAYOUT=1 CC=gcc-8 CXX=g++-8
- compiler: gcc
env: DISABLE_THREADS=1 CC=gcc-8 CXX=g++-8
- compiler: gcc
env: DISABLE_AUDIOMIXER=1 CC=gcc-8 CXX=g++-8
- compiler: gcc
env: ENABLE_GLES=1 CC=gcc-8 CXX=g++-8
- compiler: gcc
env: ENABLE_GLES=1 ENABLE_GLES3=1 CC=gcc-8 CXX=g++-8
- compiler: clang
env: CC=clang-6.0 CXX=clang++-6.0
- compiler: clang
env: C89_BUILD=1 CC=clang-6.0 CXX=clang++-6.0
- compiler: clang
env: CXX_BUILD=1 CC=clang-6.0 CXX=clang++-6.0
- os: osx
env: CC=clang CXX=clang++
- name: OSX OpenGL Make
os: osx
osx_image: xcode11.2
script:
- ./configure --enable-opengl --disable-metal
- make
- language: android
# Android builds are only supported on Trusty
dist: trusty
android:
components:
- tools
- build-tools-29.0.3
- android-29
install:
- echo y | sdkmanager "ndk-bundle"
before_script:
- export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle
script:
- cd pkg/android/phoenix
- ./gradlew assembleNormalRelease
jobs:
allow_failure:
- os: osx
before_install:
- |
if [[ "$CC" =~ ^gcc-8.* ]]; then
# Install a more recent gcc than the default
sudo apt-get install -y g++-8
elif [ "$CC" = clang-6.0 ]; then
# Install a more recent clang than the default
sudo apt-get install -y clang-6.0
elif [ "$CROSS_COMPILE" = i686-w64-mingw32- ]; then
sudo update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix
elif [ "$CROSS_COMPILE" = x86_64-w64-mingw32- ]; then
sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix
fi
before_script:
- |
if [ -n "$CROSS_COMPILE" ]; then
ARGS="$ARGS --disable-d3d8 --disable-d3d9 --disable-d3d10 --disable-d3d11 --disable-d3d12"
fi
- |
if [ -n "$DISABLE_MENU" ]; then
ARGS="$ARGS --disable-menu"
fi
- |
if [ -n "$DISABLE_OVERLAY" ]; then
ARGS="$ARGS --disable-overlay"
fi
- |
if [ -n "$DISABLE_NETWORKING" ]; then
ARGS="$ARGS --disable-networking"
fi
- |
if [ -n "$DISABLE_CDROM" ]; then
ARGS="$ARGS --disable-cdrom"
fi
- |
if [ -n "$DISABLE_VIDEO_LAYOUT" ]; then
ARGS="$ARGS --disable-video_layout"
fi
- |
if [ -n "$DISABLE_THREADS" ]; then
ARGS="$ARGS --disable-threads --disable-glslang"
fi
- |
if [ -n "$DISABLE_AUDIOMIXER" ]; then
ARGS="$ARGS --disable-audiomixer"
fi
- |
if [ -n "$ENABLE_GLES" ]; then
ARGS="$ARGS --enable-opengles"
fi
- |
if [ -n "$ENABLE_GLES3" ]; then
ARGS="$ARGS --enable-opengles3"
fi
- echo "Configure arguments = $ARGS"
script:
- ./configure $ARGS
- |
if [ -n "$C89_BUILD" ]; then
make C89_BUILD=1
elif [ -n "$CXX_BUILD" ]; then
make CXX_BUILD=1
else
make
fi
env:
global:
- ARGS=""
- MAKEFLAGS="-j2"
- secure: "qc91ReC3OlzSh2gFaSH6TYzC2qIQvgA2AZff6J13eaH8xijAhuMzttZ0rMQJ0DWCIhPeUb0kIzVyaGoe4MwPALzpw1C1AznIWiZJ53HN+hWCOcS/af7YVPk6HPySnwqrS+Wv3AIIvIKFV2mxv21F/JbT/N+pArlRrp904Xj+KPo="
addons:
apt:
packages:
- libsdl2-dev
- libusb-1.0-0-dev
- qtbase5-dev
- qtdeclarative5-dev
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-xenial
coverity_scan:
project:
name: "RetroArch"
description: "RetroArch is the official reference frontend for the libretro API."
notification_email: [email protected]
build_command_prepend: "./configure; make clean"
build_command: "make"
branch_pattern: coverity_scan
notifications:
email: true
+46
View File
@@ -11,6 +11,23 @@ RetroArch is primarily written in C89. Preserve the existing language and style
- Prefer the smallest change that completely solves the task.
- Preserve existing architecture
Read CODING-GUIDELINES before changing code: brace style, declarations,
stack budgets and the never-block rule for the main thread are there.
Subdirectories with rules of their own carry their own AGENTS.md.
`tools/compile_commands.py`, after ./configure, writes compile_commands.json
for clangd and other code navigation.
## Threads
- Nothing may block: no sleeping, busy-waiting, wait-for-idle or round-trip
waits, least of all in the threaded video wrapper and the audio pipeline.
A design that needs to block is shaped wrong.
- Do not add locks. Use retro_atomic.h, the SPSC rings (retro_spsc.h,
retro_waitable_spsc.h) and retro_eventcount.h.
- Guard threaded code with HAVE_THREADS, and check it with a full
`./configure --disable-threads` build that links, not a single TU.
- No logging inside pacing or sync paths.
RetroArch contains deliberately complex, cross-file systems. Do not simplify, consolidate, refactor, or redesign an existing subsystem merely because its structure appears unnecessarily complicated.
Complexity may exist for compatibility, platform, driver, or architectural reasons.
@@ -99,6 +116,35 @@ Historical commits may be used to understand established implementation patterns
- Do not reproduce obsolete workarounds merely because they appear in an old commit.
- Inspect surrounding current code before applying a historical pattern.
## Regression tests
Regression tests must build on both Windows and Linux.
- Do not use POSIX-only or Win32-only headers or APIs in test code without a working path for the other platform; prefer libretro-common facilities.
- Check that the test builds with a Linux toolchain and with mingw-w64 (MSYS2 MINGW64 or a cross-compiler) before submitting.
If a change touches code already covered by the GitHub CI workflow regression tests (`.github/workflows/`), run those tests locally and make sure they still pass before presenting the change.
- `tools/ci_local.py which <changed files>` lists the jobs that name them (through a sample's Makefile or a tools/ script too), the full-tree builds and the tree-wide checks.
- `tools/ci_local.py run <workflow> --job <job>` runs that job's own steps, with its configure line, flags, matrix and environment. `--dry-run` prints them, `--clean-copy` runs on a fresh worktree of HEAD plus your changes, and container jobs run in their image through docker or podman.
- Do not present a change while any of those tests fail.
- A workflow construct `ci_local.py` cannot read fails `tools/ci_local.py selftest`, which CI runs; extend the script in the same change.
- A job's Ubuntu packages go in its `APT_PKGS` and are installed once, through `.github/workflows/scripts/apt-install.sh` and its cache steps; `tools/ci_apt_check.py` fails any other `apt-get` in a runner job.
## Never commit build output
Nothing built is ever committed: no executables, objects, libraries,
archives, generated images or captures. This includes every sample and
tool harness built under samples/, libretro-common/samples/ and tools/.
- Stage files by name (`git add path/file.c`), never a directory or
`git add -A`, so a build that ran after a `make clean` cannot slip in.
- Before every commit run `git status --short` and check that each
staged path is source, a Makefile, a script or documentation.
- A new harness whose executable name the `.gitignore` patterns do not
cover (`*_test`, `*_test_*`) gets its own ignore line in the same
commit that adds the harness.
## Scope of changes
Keep changes focused.
Loaded 100 of 6902 files, more files were not shown because too many files have changed in this diff. Show more