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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
Exercise the shipping capability publishers before checking imported formats. Cover immutable device support and revocation of the published format mask.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Avoid an extra fixture reference masking consumer ownership during asynchronous GPU completion. Check that completion has not advanced before submitting the command buffer.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Drain conversions under the existing queue lock, retain queued leases until completion, and prepare native identities through owner resource work before warm imports.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
"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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
-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.
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.
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]>
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]>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
A missing CPU player leaves transport callbacks unset. Report the initial decoder failure and clean up instead of continuing into a null seek callback.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
A combination can now wait until its buttons are pushed in far
enough, chosen from 10% to 100%, so that analog triggers and
pressure-sensitive buttons (DualShock 2 and 3) only set off a hotkey,
command or macro when pressed hard. Response Time gains Press Depth
beside Instant and Hold For; a depth and a hold time can be combined,
and Instant clears both.
The depth is read as the core reads the button, through the trigger
deadzone and sensitivity. A button with no pressure reading, or one
held from the keyboard, counts as pushed all the way. It is written
in the configuration as, for example:
input_combo_4 = "l2+r2 pressed 80% : macro_1"
Only entries with a depth do the extra reads.
A DualShock 3 bound to WinUSB was only seen with the hid joypad
driver selected. It now works beside whichever joypad driver is
active (xinput, dinput, sdl2...), so it can be used together with
an Xbox pad.
The WinUSB source is started on the first poll of any joypad driver
other than hid. Its pads take device numbers from the shared device
pool, from the top down, and while it has any the joypad driver is
reached through a small stand-in that sends reads for those numbers
to the source and every other number to the driver. With no such
pad the driver is reached directly, so nothing changes per frame for
anyone without one. If the driver's own pad arrives on a number a
WinUSB pad holds, the WinUSB pad moves to another number.
With hid as the joypad driver, WinUSB pads keep their slot numbers
as before.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The presenter now owns the backend's frame side and the frame loop
only waits: each content frame carries its identity and the minimum
present interval in video_frame_info_t, the presenter marks, presents
and issues the one sleep after the frame's own present, and the frame
loop waits on the timeline semaphore for the identity it last handed
over before it samples the next frame's input. That is the same shape
on the direct path and under the threaded wrapper, which forwards the
three pokes to the driver unchanged, so Driver Pacing is no longer
turned off by Threaded Video; only Threaded Video Display Pacing, the
presenter's own hold, keeps the release to itself.
Any present after a frame's own - a repeat, a dark frame, a shader
sub-frame, a swap interval the driver emulates by presenting again -
is marked out of band on the same identity, which is what the
extension's attribution rules provide for, so Black Frame Insertion,
Shader Sub-frames and swap intervals above one no longer keep the mode
off; Scanline Sync, which paces the present itself, still does. The
Direct3D backends mark their presents the same way through a helper
around every Present, and take the interval from the frame on the
presenter's thread.
What crosses between the two threads in the Vulkan backend is
lock-free: the mode request and the presenter's answer are atomics
(a request the presenter has not seen reads as pending, never as in
effect); the swapchain the frame loop's input and simulation markers
may name is published under a generation, rewritten only while that
thread is seen outside a marker call, and a replaced swapchain is kept
until it is, so a marker never names a destroyed one. A wait the
driver does not satisfy four times running takes the mode off and
reports failure; asking again tries anew.
The fake-backend sample covers the sleep-after-present cadence, the
out-of-band markers, the cross-thread request and the kept swapchain;
the harness lane adds the threaded path through the wrapper on the
null driver and on Vulkan, and the pacing table pins the wrapper row.
Preserve the native bool type in the strict C89 geometry probe through an explicitly scoped Clang extension and exercise that compiler on Linux. Supply video state for deferred auto-state tests now reached by pacing input markers. Restrict replaceable fence-symbol injection to desktop GL and use framebuffer readback in GLES, where glGetTexImage does not exist. Keep GLES pixel and lending assertions enabled.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
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.
* 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]>
* 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]>
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
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.
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.
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.
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.
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]>
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.
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]>
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.
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.
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
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.
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
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.
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.
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.
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.
Retain the Smooth Motion presentation-path warning while keeping the English literal under the 509-character limit checked by the MSVC6 compatibility build.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
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.
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>.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
* 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]>
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.
* 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]>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Free the previous output allocation before replacing it during format negotiation. Exercise repeated format and size changes with leak checking enabled.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
- 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.
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.
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.
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.
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().
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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().
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.
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.
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.
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.
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.
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.
--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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
* 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]>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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]>
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.
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.
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.
* 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]>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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
Keep the device and drivers alive through the existing Win32 display-mode transition. Report failed mode changes through the driver reinitialization fallback.
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.
Reuse the Win32 display-mode transition and keep the device and drivers alive. Report failed transitions through the existing driver reinitialization fallback.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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
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
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.
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.
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.
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.
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.
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.
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
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
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
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
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
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.
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
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
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).
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]>
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.
* 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]>
* 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]>
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.
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.
* 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]>
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.
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
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.
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
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
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
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
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
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
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
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
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]>
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]>
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.
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.
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.
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).
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.
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.
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.
* 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]>
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.
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.
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.
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.
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.
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.
* 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]>
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.
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.
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.
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.
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.
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.
* 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]>
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.
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.
* 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]>
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]>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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]>
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Track whether the cached bind mask actually contains held keys. Clear it only after a held poll, while preserving release and bind-removal behavior. Extend the shipping frontend lane across idle, repress and removal.
Keep both publication increments defined across the signed boundary and the full counter lap. Exercise both boundaries through the shipping poll and snapshot reader.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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
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.
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.
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.
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).
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.
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.
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.
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.
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.
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]>
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.
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.
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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.
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.
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.
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.
* 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]>
* 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]>
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]>
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]>
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]>
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.
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.
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.
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.
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.
* 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]>
* 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]>
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.
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]>
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.
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.
* 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]>
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.
* 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]>
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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
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
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.
* 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]>
* 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]>
* 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]>
* 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]>
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]>
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]>
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]>
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
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.
* 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]>
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.
* 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]>
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.
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.
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).
* 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.
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.
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.
* 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]>
* 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]>
* 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]>
* 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]>
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.
* 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]>
* 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]>
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.
* 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]>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
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.
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.
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).
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).
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
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).
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
"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).
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.
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.
"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.
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.
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.
"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.
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.
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.
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.
"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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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().
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
#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.
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.
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.
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.
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.
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.
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]>
"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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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").
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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]>
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.
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.
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.
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.
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.
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.
* 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]>
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.
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.
* 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]>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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".
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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().
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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".
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
--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.
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.
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.
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.
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.
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_.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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().
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
- 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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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().
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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]>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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/.
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]>
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.
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.
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.
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.
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.
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.
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.
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.
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
5648 changed files with 893544 additions and 652893 deletions
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 \
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
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
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"
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
@@ -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
- CHEEVOS: Fix for PS2/PSP CHD hashing with RetroAchievements
- CLOUDSYNC: Google Drive cloud sync driver
- CLOUDSYNC: NFS cloud sync driver
- CONFIG: Fix saving main configuration after load configuration
- DOS: RetroArch for DOS can now start up and handle keyboard correctly
- CLOUDSYNC: Conflict resolution options
- CLOUDSYNC: Add --cloudsync command line option to run a sync and exit
- EMSCRIPTEN: Added dice to core selection dropdown
- EMSCRIPTEN: Numerous bugfixes
- FFMPEG: new features for save state, variable strength seek, audio/subtitle stream controls
@@ -39,7 +41,10 @@
- INPUT/MFI: Controller disconnection fix
- INPUT/SDL: Fix controller vid/pid detection on Windows
- INPUT/SDL: Add wiimote sensor capabilities
- INPUT: Remapped pressure sensitive buttons keep their pressure
- INPUT/SDL: Enable pressure sensitive buttons for PS3 controllers when SDL_JOYSTICK_HIDAPI_PS3=1
- INPUT/SDL3: Add SDL3 input driver
- INPUT/SDL3: Enable pressure sensitive buttons for PS3 controllers when SDL_JOYSTICK_HIDAPI_PS3=1
- INPUT/UDEV: Fix multi-touch detection
- INPUT/UDEV: Gyroscope and accelerometer sensor support
- INPUT/UDEV: Add wiimote sensor capabilities
@@ -92,7 +97,21 @@
- NETWORK: New network commands SAVE_STATE_SLOT N and GET_CONFIG_PARAM
- NETWORK: HELP network command lists every command with what it does
- NETWORK: LIST_CORES, LIST_PLAYLISTS and GET_PLAYLIST network commands, to find content and the core to load it with
- NETWORK: New network command PRESS, holding RetroPad buttons, running a number of frames and letting them go in one request
- NETWORK: New network command RUN_UNTIL, running the content until a byte at a system address meets a condition, instead of running a few frames and reading it again
- NETWORK: New network command GET_INPUT, reporting what a player holds without changing it
- NETWORK: MCP server offers the menu buttons, the disc hotkeys, the driver restarts, the state slots and the replay controls as five tools, MENU, DISK, REINIT, STATE and REPLAY, rather than nineteen; each is still a command of its own, and SAVE_FILES and LOAD_FILES are commands only
- NETWORK: New network command WATCH, so RUN_FRAMES, PRESS and RUN_UNTIL carry the bytes an assistant watches along with their own answer instead of needing a read after every step
- NETWORK: LOAD_CORE says why it failed, and neither it nor LOAD_CONTENT unloads the running core for a path that turns out not to be one
- NETWORK: SET_INPUT holds several controls at once, joined by "+": "right+a" runs and jumps in one request rather than two
- NETWORK: LIST_CORES takes a content path, listing only the cores that can load it
- NETWORK: MCP server: AI assistants can drive RetroArch through the Model Context Protocol, every network command a tool (off by default, see docs/mcp-server.md)
- NETWORK: MCP server: tools whose work runs over later frames (content, states, replays, shaders, AI service) answer with its outcome once it is through, and the server stays up across content loads
- NETWORK: MCP server: the replies that are rows of fields are given as structuredContent as well as text, with an outputSchema describing their columns
- NETWORK: MCP server: the hotkeys that step a selection a program cannot read back are no longer offered as tools, where a command taking the value outright already is; all of them remain network commands
- NETWORK: Fix SAVE_STATE_SLOT freezing RetroArch with cores such as bsnes
- NETWORK: Fix the SAVE_FILES and LOAD_FILES failure reply, and LOAD_CORE reporting a core that failed to load as loaded
- NETWORK: Fix LOAD_CONTENT closing the running content before finding that the core or content path it was given does not exist
- NETWORK: Built-in TLS 1.2/1.3 client; mbedTLS and BearSSL are no longer bundled (--enable-mbedtls / --enable-bearssl use a system one)
- NETWORK: Built-in SMB2/3 client with signing, AES-CCM/GCM encryption and Kerberos; libsmb2 is no longer bundled (--enable-libsmb uses a system one)
- NETWORK: Built-in NFSv3 and NFSv4 (4.0, 4.1, 4.2) client for loading content from nfs:// exports
Do not use VLAs (Variable Length Array) in C source files. These are not
C89-compliant.
Struct initialization
---------------------
If a struct is set in stone - its ABI is fixed and not subject to change -
prefer initializing its members by hand over a memset. This covers the
structs of published APIs (Vulkan, OpenXR, Direct3D/DXGI, XInput, Win32,
Xlib and the like), whose layout cannot change without breaking every
program built against them.
- Write every member the struct has, zero or not, so nothing is left
undefined.
- A struct the API fills in (a query, a properties or a state struct)
needs only its chain (sType/type and pNext/next, or the size member).
The API defines its outputs once the call succeeds; a member read
without that check, or after a call that returns nothing, is set
too.
- Of a union, write the member that is in use in full; a view
description sets the member its dimension selects.
- Keep the memset for the tree's own structs, which can still grow;
for structs whose member names differ between the SDKs or C
libraries we build with (OVERLAPPED, struct input_event,
struct sigaction), where a member store that compiles against one
header breaks the build against another; and for structs with
bit-fields or reserved members (the Vulkan Video Std* parameter
sets), which member stores cannot clear whole.
- Several statements replace one: give a single-statement if/else body
its braces when its memset becomes member stores.
Main thread and task handlers
-----------------------------
The UI and the main thread must never block. When Threaded Tasks is
@@ -275,6 +302,8 @@ while the menu is up or a core runs, each commented where it is:
* the save/load state waits on exit, at startup and when an init
fails (content_wait_for_save_state_task() and
content_wait_for_load_state_task());
* the cloud sync worker on exit, bounded, so no blocking driver
call runs into the teardown (cloud_sync_deinit());
* the Wii U main loop's vsync wait (platform_wiiu.c).
Win32 file dialogs are modal by nature and stay so.
Loaded 100 of 5648 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.