Skip to content

OpenXR: one core for the desktop and Quest headsets, with head tracking, controllers, settings and desktop OpenGL - #19811

Merged
LibretroAdmin merged 31 commits into
libretro:masterfrom
Davey-Hughes:openxr-unify
Oct 12, 2026
Merged

LibretroAdmin merged 31 commits into
libretro:masterfrom
Davey-Hughes:openxr-unify

Conversation

@Davey-Hughes

@Davey-Hughes Davey-Hughes commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Guidelines

  1. Rebase before opening a pull request
  2. If you are sending several unrelated fixes or features, use a branch and a separate pull request for each
  3. If possible try squashing everything in a single commit. This is particularly beneficial in the case of feature merges since it allows easy bisecting when a problem arises
  4. RetroArch codebase follows C89 coding rules for portability across many old platforms check using C89_BUILD=1

Description

One OpenXR core for every headset host. The desktop Vulkan host and the Quest's Vulkan and GLES hosts now share the instance, session, swapchain, frame loop, tracking and controller code. The desktop gains head tracking, and the desktop OpenGL drivers gain headset output.

Commits, oldest first:

  1. menu: the cursor stays on Headset Output when Stereo Mode comes and goes.
  2. openxr: a session without focus is not logged as controllers not read.
  3. libretro.h: RETRO_VIDEO_VIEWS_FLAG_TRACKED marks a head-tracked eye pair, and HMD is how a core finds a headset. REQUEST_FLAT and ipd_hint_m are removed (see "API change" below).
  4. The runtime, session, swapchains and frame loop are graphics-neutral units in gfx/common/openxr_*. vulkan_openxr.c keeps only the Vulkan side.
  5. One host interface reports the headset's state on the desktop and the Quest. HMD comes with PRESENTS and STEREO, and only while the session runs. The pose calls go to the host.
  6. Desktop head tracking:
    • a TRACKED map's eyes become a projection layer, drawn with the poses the core was given, in the LOCAL or STAGE space it asked for;
    • Recenter moves a seated world with the screens;
    • a paused core's world survives a driver reinit;
    • RECENTERED and TARGET_RESIZED are reported.
  7. The Quest's Vulkan headset runs in-line on the shared core.
  8. The Quest's GLES headset runs on the shared core.
    • It needs ES3, so the quest flavour now builds with GLES=3.
    • glcore gets the flat context.
    • An untracked view map shows one view per eye, as on Vulkan: LEFT to the left eye, RIGHT to the right, NONE to both. A stereo pair no longer shows side by side in each eye.
  9. One controller module for both platforms:
    • the Quest's own module goes;
    • Touch's menu button is Start from the press, and opens the menu when let go after a second;
    • the laser can point at a window shown whole.
  10. Headset settings show for every driver with a headset path.
    • New Screen Placement setting: Fixed in Room, Follow Head or Lazy Follow.
    • Recenter works in the menu, and moves a paused seated world in the same headset frame.
    • The Quest asks for a refresh rate that fits the content and repeats frames to fill it.
  11. The headset's controllers are a controller: joypad slots with built-in profiles. Hotkeys, port binds, remaps and rumble work with them on both platforms. They leave their slot when the headset session ends while the window keeps running.
  12. gl: headset output for the desktop OpenGL drivers, on Linux.
    • glcore and gl bind the session through XR_KHR_opengl_enable (GLX) or XR_MNDX_egl_enable (EGL). The XR thread holds a context in the window's share group and copies each finished image into the swapchain.
    • glcore draws screens, the menu and a tracked map's eyes 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.
    • An image is only ever shown with the quads or eye poses it was drawn with, and the core is woken for its next frame after the last one is taken, so none is shown a headset frame early.
    • OpenXR now configures with OpenGL alone (--disable-vulkan), and with either GL binding alone (--disable-egl, --disable-x11).
  13. docs/openxr.md: what the headset path does, how it is built, and how a headset, graphics API or platform is added.

API change (commit 3): cores that use the video views API need the new libretro.h.

  • RETRO_VIDEO_VIEWS_FLAG_REQUEST_FLAT is removed: a map without TRACKED is flat.
  • RETRO_VIDEO_VIEWS_FLAG_TRACKED is (1 << 0).
  • ipd_hint_m is removed from struct retro_video_views, which moves the two recommended-size members.

How it is built, and adding another headset (details in docs/openxr.md):

  • Shared, with no graphics API in it: the runtime and instance (openxr_runtime), the session (openxr_session), the swapchains (openxr_swapchain), the frame loop with pacing, screen placement and recenter (openxr_frame), head tracking for cores (openxr_host, openxr_tracking), and the controllers and laser (input_openxr).
  • A host joins those to one graphics API. There are three: vulkan_openxr.c (Linux and the Quest), gl_openxr.c (desktop OpenGL) and gl_android_openxr_ctx.c (the Quest's GLES).
  • Another headset on a platform that already has a host needs no video code: at most a controller profile, and on Android its loader set-up and a build flavour.
  • Another graphics API or platform needs a host: a graphics binding, the host itself, and the video driver's draw-and-publish calls.

Visible changes for Quest users:

  • Screens are fixed in the room.
  • The controllers appear as "OpenXR Controllers". A saved "Meta Quest Touch Plus Controller" profile and its remaps no longer apply.
  • The menu opens on release after holding the menu button for a second.
  • The headset runs at a refresh rate chosen for the content.

Building and trying it:

  • Linux: ./configure --enable-openxr. OpenXR is opt-in and Linux-only on the desktop; it needs Vulkan or OpenGL, and threads. The Khronos loader is built in-tree, so there is nothing to install.
  • Quest: the quest gradle flavour, as before. It now builds with GLES=3.
  • At run time an OpenXR runtime has to be active (SteamVR or Monado). Then turn on Settings > Video > Stereo 3D & Screens > Headset Output (OpenXR), with the vulkan, glcore or gl video driver.

Tested on Linux, on this head (master 57c9edf):

  • Every commit builds alone with --enable-openxr and passes the stand-in runtime tests; commits 3 to 12 also build with the Android NDK (quest flavour). The tip also builds GLX-only, EGL-only, without Vulkan and without OpenXR.
  • The stand-in runtime tests pass under ASan/UBSan and TSan, and the threaded-video harness passes with OpenXR on.
  • Headset end-to-end suite (Monado's simulated headset in headless gamescope):
    • Vulkan, validation on: 139/139;
    • OpenGL lanes (glcore on GLX and Wayland EGL, gl on X11 EGL and GLX): 241/241, two pacing cases on a rerun alone;
    • views suite: 163/163.
  • C89 on the touched objects, and the source checkers.
  • Windows (MSYS2 UCRT64): RetroArch builds, and the five portable unit tests build and pass.
  • On a Steam Frame (SteamVR, native build, worn): Vulkan, glcore and gl showed the screens, the menu, a recenter and a video reinit; Vulkan and glcore showed a head-tracked scene. Frame recordings of the screen runs show each core frame held for two headset frames at 120 Hz.

Related Issues

None.

Related Pull Requests

Core PRs that use this API will follow soon for Beetle VB, libretro-3dengine, boom3, and Azahar. They need the libretro.h from this PR.

Beetle VB, 3dengine, and boom3 have PRs linked above that should be merged after this.

Reviewers

@cscd98 for the Quest side.

Davey-Hughes and others added 12 commits October 12, 2026 04:42
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 <1188869+cscd98@users.noreply.github.com>
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 <1188869+cscd98@users.noreply.github.com>
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 <1188869+cscd98@users.noreply.github.com>
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 <1188869+cscd98@users.noreply.github.com>
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 <1188869+cscd98@users.noreply.github.com>
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 <1188869+cscd98@users.noreply.github.com>
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 <1188869+cscd98@users.noreply.github.com>
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 <1188869+cscd98@users.noreply.github.com>
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 <1188869+cscd98@users.noreply.github.com>
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 <1188869+cscd98@users.noreply.github.com>
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 <1188869+cscd98@users.noreply.github.com>
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 <1188869+cscd98@users.noreply.github.com>
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 <1188869+cscd98@users.noreply.github.com>
@cscd98

cscd98 commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Tested 3DEngine and Beetle VB, working great 👍 Also tested boom3, working.

Davey-Hughes added a commit to Davey-Hughes/libretro-3dengine that referenced this pull request Oct 12, 2026
The header from RetroArch's unified OpenXR pull request
(libretro/RetroArch#19811). REQUEST_FLAT and ipd_hint_m are gone from
retro_video_views: a flat map is one without TRACKED, and OpenXR always
gives real eye poses.
Davey-Hughes added a commit to Davey-Hughes/beetle-vb-libretro that referenced this pull request Oct 12, 2026
The header from RetroArch's unified OpenXR pull request
(libretro/RetroArch#19811). REQUEST_FLAT and ipd_hint_m are gone from
retro_video_views: a flat map is one without TRACKED, and OpenXR always
gives real eye poses.
Davey-Hughes added a commit to Davey-Hughes/libretro-3dengine that referenced this pull request Oct 12, 2026
The header from RetroArch's unified OpenXR pull request
(libretro/RetroArch#19811). REQUEST_FLAT and ipd_hint_m are gone from
retro_video_views: a flat map is one without TRACKED, and OpenXR always
gives real eye poses.

Co-authored-by: Craig <1188869+cscd98@users.noreply.github.com>
Davey-Hughes added a commit to Davey-Hughes/beetle-vb-libretro that referenced this pull request Oct 12, 2026
The header from RetroArch's unified OpenXR pull request
(libretro/RetroArch#19811). REQUEST_FLAT and ipd_hint_m are gone from
retro_video_views: a flat map is one without TRACKED, and OpenXR always
gives real eye poses.

Co-authored-by: Craig <1188869+cscd98@users.noreply.github.com>
Davey-Hughes added a commit to Davey-Hughes/boom3 that referenced this pull request Oct 12, 2026
Taken whole from RetroArch's unified OpenXR pull request
(libretro/RetroArch#19811). REQUEST_FLAT and ipd_hint_m are gone from
retro_video_views; a map with RETRO_VIDEO_VIEWS_FLAG_TRACKED is the
head-tracked view, and RETRO_VIDEO_VIEWS_STATUS_HMD is how a core
learns of a headset.

Co-authored-by: Craig <1188869+cscd98@users.noreply.github.com>
codex and others added 6 commits October 12, 2026 15:09
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.
Fail early when HARNESS_VIDEO_DRIVER differs from the initialized backend so a missing driver cannot qualify another backend's lanes.
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.
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.
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.
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.
LibretroAdmin and others added 12 commits October 12, 2026 15:34
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).
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.
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.
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.
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.
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.
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.
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.
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 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.
Apply PR libretro#19811 at 3d4ad57 to master at 204f03c, 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.
@LibretroAdmin
LibretroAdmin merged commit 5103a23 into libretro:master Oct 12, 2026
125 of 134 checks passed
LibretroAdmin pushed a commit to libretro/beetle-vb-libretro that referenced this pull request Oct 12, 2026
* Allocate the frame for the widest 3D mode

FB_WIDTH followed the live 3D mode while the surface keeps its load-time
size, so changing the mode to side-by-side at run time handed video_cb
a pitch wider than the buffer.

Co-authored-by: Craig <1188869+cscd98@users.noreply.github.com>

* Update libretro.h for the unified VR view API

The header from RetroArch's unified OpenXR pull request
(libretro/RetroArch#19811). REQUEST_FLAT and ipd_hint_m are gone from
retro_video_views: a flat map is one without TRACKED, and OpenXR always
gives real eye poses.

Co-authored-by: Craig <1188869+cscd98@users.noreply.github.com>

* Show both eyes through the unified view API

A core learns about a headset only from the HMD status bit;
SET_VIDEO_VIEWS returning true means the map was accepted. Entering VR
on that true left desktop frontends blank.

VR enabled now sends the two eyes side by side as an untracked
LEFT/RIGHT map whenever the frontend presents both eyes, in a headset or
a stereo display mode, and needs no GL, so it works in every build. The
chosen 3D mode comes back when the frontend stops presenting both eyes.

Head-locked, a new vb_vr value in HAVE_VR builds, keeps the GL renderer:
it enters on the first frame with HMD and sends a TRACKED map. A video
reinit restarts the headset session and leaving VR changes the geometry,
so a lost headset is waited for five seconds before the core falls back
to the stereo screen. A frontend that reports a headset but refuses the
tracked map is not asked again until the headset drops or head-locked
is turned off.

The map in use is sent every retro_run(), as libretro.h asks, except
on the frames that wait for a lost headset: it covers state loads and
run-ahead. The frontend writes the recommended eye size back on each
tracked map, so a new size there is taken as a resize.

The VR options were always shown because libretro_vr.h defines
VB_VR_ENABLED as 0 or 1; they now test its value.

HAVE_VR builds, Android's among them, no longer ask for GL unless
head-locked is chosen: enabled now means the stereo screen.

Co-authored-by: Craig <1188869+cscd98@users.noreply.github.com>

---------

Co-authored-by: Craig <1188869+cscd98@users.noreply.github.com>
Hancock33 added a commit to Hancock33/batocera.piboy that referenced this pull request Oct 13, 2026
-----------------------------------------------------------------------------------
eden.mk 9580e54f2f4787986abde4c7601177d0c5cd2792 # Version: Commits on Oct 12, 2026
-----------------------------------------------------------------------------------
[dist, android] Update translations from Transifex for Oct 12 (#4527)

--------------------------------------------------------------------------------------
openmsx.mk c596782adf9ebe43b1ab8f3756261d4a6c0b7c56 # Version: Commits on Oct 11, 2026
--------------------------------------------------------------------------------------
YM2608: load the rhythm samples from an external ROM
The six rhythm sounds are a ROM inside the chip. That data was compiled
into openMSX. Load it through a <rom> tag instead, like the wave ROM of
the MoonSound, so that the user supplies it (see #2238). The ROM is
mandatory: without it the extension does not start.,

----------------------------------------------------
pcsx2.mk v2.9.122 # Version: Commits on Oct 12, 2026
----------------------------------------------------
- [GS/HW: Improve double clear and overlap detection](PCSX2/pcsx2#15054)

-------------------------------------------------------------------------------------
ppsspp.mk b419238f81fd44740ac9bf638b951d5c9bbf154b # Version: Commits on Oct 12, 2026
-------------------------------------------------------------------------------------
Merge pull request #22526 from hrydgard/crash-review-jni-ui
Claude code review: Fix crashes in the Android JNI layer and UI screen lifetimes,

------------------------------------------------------------------------------------
tic80.mk 8fa6ca77765cbf58c34f4acf5d2c0d4df20363a9 # Version: Commits on Oct 12, 2026
------------------------------------------------------------------------------------
format: the .clang-format describes the code, and the lint job holds a change to it (#3078),

----------------------------------------------------------------------------------------
retroarch.mk a9c5823eba6e369bf4148d54aed4c4a8e0e1873b # Version: Commits on Oct 12, 2026
----------------------------------------------------------------------------------------
D3D11: views through a preset
The preset's chain moves into d3d11_chain_t, drawn by functions that
take it. Each view gets a copy of the main chain: its shaders and
lookup textures referenced, its semantics pointed at its own values and
textures, its own render targets and history, the preset's parameters
shared, so nothing is compiled or read again. A view is read where it
lies in a hardware frame when every pass can and there is no history,
else copied out. Views no longer fall back to the whole frame with a
preset; the end to end preset cases run on d3d11.,

-----------------------------------------------------------------------------------------------------
libretro-beetle-saturn.mk faef2779a7568c1734addc4fe63cc76e9fa2566b # Version: Commits on Oct 12, 2026
-----------------------------------------------------------------------------------------------------
Lighter savestates and a zero-copy CD sector cache
A state now carries VDP1's side framebuffers only while the feature
that reads them is on: the alternate field with the progressive
deinterlacer, the mesh buffers with improved mesh transparency. They
are enhancement buffers, not Saturn state, and nothing reads them
otherwise. A byte records which are present; older states carry all
three and still load. One left out is rebuilt on load from what its
feature starts a frame with. With default options a state shrinks from
6.32 to 4.75 MB. retro_serialize_size reports the largest state the
session can write, so switching a feature on later never fails a save;
a shorter state is zero-padded, and loading stops at the length the
state records.
Loading no longer rebuilds a strcmp name map for every section:
records come back in the order they were written, so each is matched
against the next entry first and searched for only out of order. The
duplicate-name check stays in debug builds. One-byte bools are written
in one call.
The CD reader thread reads sectors straight into slots the emulation
thread then keeps as its 256-sector cache, instead of copying each
sector out of a 32-slot pool. Every slot taken from the reader returns
one to it, the evicted one or one from a reserve, so 32 slots keep
circulating and both queues stay bounded. A subchannel-only read now
copies the 96 bytes it returns rather than the whole sector.
Video, audio and savestate hashes match the previous build, threaded
and with threading disabled; states round-trip across old and new
cores and through a run-ahead loop exactly. Loading is 20-26% faster
and saving up to 14% faster on the test machine.,

-------------------------------------------------------------------------------------------------
libretro-beetle-vb.mk d5ae54c8e23b573a3b34e888b2d48a588027e92c # Version: Commits on Oct 12, 2026
-------------------------------------------------------------------------------------------------
Show both eyes through the unified VR view API (#96)
* Allocate the frame for the widest 3D mode
FB_WIDTH followed the live 3D mode while the surface keeps its load-time
size, so changing the mode to side-by-side at run time handed video_cb
a pitch wider than the buffer.
Co-authored-by: Craig <1188869+cscd98@users.noreply.github.com>
* Update libretro.h for the unified VR view API
The header from RetroArch's unified OpenXR pull request
(libretro/RetroArch#19811). REQUEST_FLAT and ipd_hint_m are gone from
retro_video_views: a flat map is one without TRACKED, and OpenXR always
gives real eye poses.
Co-authored-by: Craig <1188869+cscd98@users.noreply.github.com>
* Show both eyes through the unified view API
A core learns about a headset only from the HMD status bit;
SET_VIDEO_VIEWS returning true means the map was accepted. Entering VR
on that true left desktop frontends blank.
VR enabled now sends the two eyes side by side as an untracked
LEFT/RIGHT map whenever the frontend presents both eyes, in a headset or
a stereo display mode, and needs no GL, so it works in every build. The
chosen 3D mode comes back when the frontend stops presenting both eyes.
Head-locked, a new vb_vr value in HAVE_VR builds, keeps the GL renderer:
it enters on the first frame with HMD and sends a TRACKED map. A video
reinit restarts the headset session and leaving VR changes the geometry,
so a lost headset is waited for five seconds before the core falls back
to the stereo screen. A frontend that reports a headset but refuses the
tracked map is not asked again until the headset drops or head-locked
is turned off.
The map in use is sent every retro_run(), as libretro.h asks, except
on the frames that wait for a lost headset: it covers state loads and
run-ahead. The frontend writes the recommended eye size back on each
tracked map, so a new size there is taken as a resize.
The VR options were always shown because libretro_vr.h defines
VB_VR_ENABLED as 0 or 1; they now test its value.
HAVE_VR builds, Android's among them, no longer ask for GL unless
head-locked is chosen: enabled now means the stereo screen.
Co-authored-by: Craig <1188869+cscd98@users.noreply.github.com>
---------
Co-authored-by: Craig <1188869+cscd98@users.noreply.github.com>,

---------------------------------------------------------------------------------------------
libretro-cap32.mk 2d41a1f07e654e97c4f24ab8447c3fbd7d728bc8 # Version: Commits on Oct 12, 2026
---------------------------------------------------------------------------------------------
doc: Updated changelog,

----------------------------------------------------------------------------------------------
libretro-ppsspp.mk b419238f81fd44740ac9bf638b951d5c9bbf154b # Version: Commits on Oct 12, 2026
----------------------------------------------------------------------------------------------
Merge pull request #22526 from hrydgard/crash-review-jni-ui
Claude code review: Fix crashes in the Android JNI layer and UI screen lifetimes,

---------------------------------------------------------------------------------------------
libretro-tic80.mk 8fa6ca77765cbf58c34f4acf5d2c0d4df20363a9 # Version: Commits on Oct 12, 2026
---------------------------------------------------------------------------------------------
format: the .clang-format describes the code, and the lint job holds a change to it (#3078),
@Davey-Hughes
Davey-Hughes deleted the openxr-unify branch October 15, 2026 23:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants