103 Commits
Author SHA1 Message Date
spoopyghosty0 fc8a28b3cd Foveation: centre the full-density bins, not Valve's texels (#394)
Turnip renders each GMEM bin at the density the map has at the bin's centre, on
a grid anchored in map space, so the full-density area is the bins whose centre
falls in Valve's sharp texels: up to half a bin off the texels' centre the
centring layer aimed at (In Death 2768x2768: 192x224 bins, -80,+32 px in both
eyes). native/foveation/tu_bins.h recomputes Turnip's bin size per foveated pass
as Mesa does (tu_render_pass_gmem_config, tu_tiling_config_update_tile_layout,
the LRZ FDM limit; AVOID_CCU layout; Adreno 750 only, Mesa 26.3 window); the
layer tracks render-target images/views, models render passes and dynamic
rendering, and centres those bins on each eye. Unmodelled cases keep the texel
centre. Adapter settings fdm_bin_w/fdm_bin_h override the size (FrameBridge ->
fp_fdm_set_bin). No frame.adapter revision bump: games get it on rebuild.

Headless (darkening test, snapshot=15, Fixed), block centre vs optical centre:
In Death L -76,+31 / R -83,+35 px -> L +4,-1 / R -3,+3; AC Nexus (2592x2256,
computed 192x224) L +1,+5 / R +2,0; fdm_bin_w=288/fdm_bin_h=256 shifted the
block by the predicted -96,+48.
2026-10-25 19:59:10 -04:00
spoopyghosty0 6988d54d5c FrameBridge: a refused refresh-rate request keeps the current rate instead of failing the game (OutRun quit on XR_ERROR_DISPLAY_REFRESH_RATE_UNSUPPORTED_FB, #336); triage refresh-rate-refused 2026-10-25 13:39:25 -04:00
spoopyghosty0 1262880f14 FrameBridge space_warp_emul: XR_FB_space_warp for games that require it (OutRun Quest VR, #336)
OutRun quits with "AppSW recommended buffers unavailable" whether XR_FB_space_warp is offered or hidden.
New per-game adapter setting space_warp_emul (off by default, native/adapter/space_warp.c): offers the
extension (emulated when the runtime lacks it, overrides hide_space_warp), reports a motion-vector size of
eye/4 (the Frame's runtime answers the full 1728x1728 eye), lists RGBA16F and depth formats after the
runtime's, serves motion-vector swapchains (RGBA16F/RG16F at the reported size or at most half the eye) and
their depth swapchains as FrameBridge-owned GL textures / Vulkan images (3 images, round robin, wait/release
in front of every other hook), and strips XrCompositionLayerSpaceWarpInfoFB at xrEndFrame (eye depth info is
now kept when space warp info isn't first in the chain, also for hide_space_warp).

Headless on the dev Frame with Pinball FX VR (UE5 + OVRPlugin, Vulkan): both 376x376 pairs served,
600/599/598 acquire/wait/release by FrameBridge, space warp info removed, 0 xrEndFrame failures, 72.2 fps like
its own recipe. Triage appsw-required, catalog entry for OutRun (fb_color_space + space_warp_emul),
PLAYBOOK row, DEVICE_NOTES section, host harness for the GL/Vulkan paths.
2026-10-24 15:01:03 -04:00
spoopyghosty0 a4546e4ee4 Foveation: put Valve's sharp area on each eye (#394), second version; Vulkan eye snapshots
Valve's VALVE_fdm_injection layer gives every eye swapchain a density map covering the whole swapchain with its
FOVE_LEVEL rectangle at the swapchain centre; it never uses the submitted imageRect or the eye's optical axis, so games
with smaller or shifted eye rects (dynamic resolution, AC Nexus 0,0 / 287,0 1726x1728 in 2016x1728) got the sharp area
~14% of the view to the side. VK_LAYER_FRAMEPORT_fdm_centre (native/foveation/centre.h, second layer in
libVkLayer_fdm_darken.so, right below Valve's layer when FDM_DEBUG has "enable"; not for The Climb 2;
FRAMEPORT_FDM_CENTRE=0 turns it off) replaces the per-eye density-map offsets: the eye's optical centre (Fixed) or
Valve's gaze re-based on the eye's rect (Eye-tracked). FrameBridge reports the views (fdm_views.c).

Changes after the first version (b2be40af, reverted 2ce1c505) failed in the headset:
- Eye-tracked black: Valve declares fragmentDensityOffsetCount 1 for two-view passes while Turnip reads one offset
  per view regardless, so the right eye's offset came from uninitialised stack memory. Every offset Turnip reads is
  now written (view count from the pass's view mask, eye from the map view's layer, each eye's gaze from Valve's array).
- Fixed crash: the layer no longer flags images (it had flagged external-memory images Valve leaves alone). Valve's
  own disable_offsets path also crashed AC Nexus without this layer (Turnip under fdm_injection_CmdBeginRendering), so
  Fixed now drops disable_offsets in-process once the layer is in the chain and overrides Valve's gaze offsets with the
  optical centre; if Valve read the option first its maps carry no offset flag and passes are left alone.

Headless on the dev Frame, AC Nexus, 130 s per run, darkening test, snapshots every 15 s: centring Fixed (2 runs) and
Eye-tracked ran without crash at 72 fps, both eyes' sharp area within 2% of the optical centre (Valve only: +14% Fixed,
+25% left eye Eye-tracked; Valve-only Fixed crashed in 1 of 2 runs). FrameBridge snapshot=N reads Vulkan eye images
back; FRAMEPORT_FOVEATION_TEST=2 paints 1/fragment area. Triage valve-fdm-render-crash. Host harness
tests/test_fdm_centre.py. The sharp area's size still snaps to Turnip's GMEM bins (driver design).
2026-10-24 11:58:39 -04:00
spoopyghosty0 2ce1c50544 Revert the foveation centring layer (b2be40af): AC Nexus crashed in Valve's fdm layer (Fixed) and showed black (Eye-tracked) in the headset (#394); back until it is fixed and checked headless 2026-10-24 11:14:55 -04:00
spoopyghosty0 b2be40af35 Foveation: put Valve's sharp area on each eye (#394); Vulkan eye snapshots
Valve's VALVE_fdm_injection layer gives every eye swapchain a density map that covers the whole swapchain and centres
its FOVE_LEVEL rectangle there; it never uses the submitted imageRect or the eye's optical axis. Games that submit a
smaller or shifted rect (dynamic resolution, asymmetric eye rects such as AC Nexus 0,0 / 287,0 1726x1728 in a
2016x1728 swapchain) got the sharp area ~14% of the view off to the side, towards the bottom for corner-anchored rects.

VK_LAYER_FRAMEPORT_fdm_centre (native/foveation/centre.h), a second layer in libVkLayer_fdm_darken.so inserted right
below Valve's layer when FDM_DEBUG has "enable" (not for The Climb 2; FRAMEPORT_FDM_CENTRE=0 turns it off), rewrites
the per-eye density-map offsets at the end of each pass: Fixed puts the sharp area on the eye's optical centre (it adds
the offsets and the offset image flag Valve leaves out with disable_offsets), Eye-tracked re-bases Valve's gaze offset
on the eye's rect. FrameBridge reports the views (fdm_views.c: swapchain size, layer, imageRect, optical centre).

FrameBridge snapshot=N now also reads Vulkan eye images back (both eyes, half size, rect + FOV logged), and
FRAMEPORT_FOVEATION_TEST=2 paints 1/fragment area. Headless on the dev Frame (AC Nexus, Fixed, darkening test):
sharp-area centre vs optical centre +203,+43 px -> +26,+11 at 1x, +276,+65 -> +12,+17 at 1.5x, +407,+87 -> +47,+31
at 2x. The size still snaps to Turnip's GMEM bins (density is chosen per bin), so Small equals Large at 1x and the
shape changes between render scales; that part is the driver's.

Docs: DEVICE_NOTES "Foveation: sharp area centre and size", FRAME_RUNTIME, PLAYBOOK row, native/README; triage
dynamic-resolution-foveation is quiet once the centring layer ran. Host harness tests/test_fdm_centre.py.
2026-10-24 10:28:42 -04:00
spoopyghosty0 aff56927ec FrameBridge: flipped Vulkan quads are shown as upside-down atlas copies made every frame instead of flipping the app's image in place (AC Nexus' late-drawn notices stayed upside down, re-flips and GPU waits; likely the white start flash)
The in-place flip raced Unity, which finishes some panels after xrEndFrame without acquiring them again, and its
blits/readbacks waited for the GPU inside xrEndFrame. swapchain_pool.c now owns flips (also without swapchain_pool):
a flagged pooled panel's atlas copy is a blit with reversed rows, a flagged runtime swapchain's quad gets a per-frame
atlas area with an upside-down blit of its released image. The app's images are only read; no CPU waits. flip_dump
reads back what the runtime is given (async); layer_debug logs copies and waits per 5 s. The late-drawing
fingerprints, scratch image and in-place flip are gone. Headless: every intro panel upright as submitted, 72 fps.
2026-10-24 00:28:10 -04:00
spoopyghosty0 02eb17dd91 FrameBridge: back to the sparse late-drawing check (revert fdaba13f): the every-pixel async version brought AC Nexus' white start flash back without fixing its notice panel (owner, headset 2026-10-23); the 595d12a0 state was headset-verified 2026-10-23 23:53:31 -04:00
spoopyghosty0 fdaba13f51 FrameBridge: late-drawing check compares every pixel of rarely acquired panels (a sparse sample missed AC Nexus' text-only notice), read back asynchronously so frames never wait on it 2026-10-23 19:14:22 -04:00
spoopyghosty0 595d12a038 FrameBridge: a flipped panel the app finishes drawing after xrEndFrame (no new acquire) is flipped again once its content settles (AC Nexus' calibrate-arms screen stayed upside down) 2026-10-23 17:56:31 -04:00
spoopyghosty0 9793e6dbae FrameBridge flip_dump: a quad still empty whenever it was flipped is saved ~1 s later as shown ("late") 2026-10-23 17:34:28 -04:00
spoopyghosty0 4ab6b670d2 FrameBridge: flips and alpha inversion use the last released image (the one a layer shows), not the last acquired one; layer_debug logs quad swapchains/rects and each swapchain's first flips, flip_dump adds the image as shown ~3 s later 2026-10-23 17:22:43 -04:00
spoopyghosty0 e3e8fc4388 FrameBridge: a flip blit still running after 100 ms counts as done (it was flipped again next frame, so AC Nexus' panels stayed upside down at random); the shared command buffer waits for it before reuse 2026-10-23 17:00:21 -04:00
spoopyghosty0 4eff40ca64 swapchain_pool (Vulkan): pool chained and mipmapped panels, log every decline (#424)
The first device run (AC Nexus, Quest 3) pooled nothing and said nothing
about why. The pool declined any chained create info and any mipCount
other than 1; on Vulkan it now accepts a XrSwapchainCreateInfoFoveationFB
(irrelevant to a private image) and honours XrVulkanSwapchainCreateInfoMETA's
extra create flags and usage, and keeps the app's mip levels (barriers
cover every level, level 0 goes into the atlas). GLES keeps its rules.

Every decline is logged once per reason ("swapchain_pool: not pooled:
..."): session mismatch (also from the proactive check), array/faces,
samples, mips, usage, flags, size, format, chained struct type, Vulkan
device, GL context, slots, atlas refused, image allocation, atlas full;
plus one line when the 8-swapchain trigger starts pooling. The
xrCreateSwapchain log gains mips= and the first chained struct type.

Harness: the device sequence (1728 eye set, 2x2 and 1024x1024 usage 0x39,
2016 eye set, twelve 0x39 panels with foveation/META chains and a 4-mip
panel, flip-tracked runtime creates) pools every panel and the 2592x2256
set then fits under a 15-swapchain cap; decline reasons are checked.
2026-10-23 16:22:39 -04:00
spoopyghosty0 7a194ccaa0 Fix #424: Vulkan panel pool for swapchain_pool, AC Nexus back to Quest 3
swapchain_pool now also covers Vulkan sessions (XR_KHR_vulkan_enable/2):
from eight live app swapchains, single-layer format-43 colour panels get
private device-local VkImages (app usage + transfer, returned as
XrSwapchainImageVulkanKHR, UNDEFINED -> COLOR_ATTACHMENT_OPTIMAL at the
first acquire) packed into up to two 4096x4096 sRGB runtime atlases.
xrEndFrame records one command buffer per frame on the binding's queue
(3-deep fence ring, submits under flip_lock): vkCmdCopyImage from each
referenced panel into the waited atlas image, which returns to
COLOR_ATTACHMENT_OPTIMAL before its release. Image ownership, crops and
layer ordering follow the GLES pool; the whole layer rewrite holds the
pool lock so a destroy cannot free an image with recorded copies.
Pooled Vulkan panels stay tracked by flip_vk.c (in-place flips keep
working). Other formats and arraySize-2 eye images keep runtime
swapchains. GLES behaviour is unchanged.

AC Nexus: Quest 3 identity again with swapchain_pool=1 (min_app 1.3.1),
frame.adapter revision 3 for it only. Settings text, triage diagnosis,
PLAYBOOK, DEVICE_NOTES, native README, PATCHES.md and the i18n template
updated. Host harness gains a recording Vulkan double (layouts, copy
regions, ring, failures, cleanup, AC Nexus swapchain count: 12 live with
the new eye set). Not verified on the device.
2026-10-23 16:11:59 -04:00
spoopyghosty0 e231c5a6fe scene: drop PR #246's per-call semantic-label log line (it logged every call; mr-scene logs each entity's labels once), as in the PR's merge 2026-10-19 00:23:18 -04:00
spoopyghosty0 7a65e86cdc FrameBridge layer limit: FrameBridge's own fade quad is always kept with the projection layers, the app's other layers fill the rest in submission order; one log line per trim (at most 5), trimmed layers no longer counted as unusable 2026-10-19 00:23:15 -04:00
alicecatpaw a0c26c9646 Emulate Meta spatial entity discovery and enforce runtime layer limits
- Emulate XR_META_spatial_entity_discovery (xrDiscoverSpacesMETA, xrRetrieveSpaceDiscoveryResultsMETA) and XR_META_boundary_visibility
- Add system properties for space discovery and boundary visibility
- Fix semantic labels length and null termination
- Allow xrGetInstanceProcAddr with null handle for emulated procs in xrshim
- Trim composition layers exceeding runtime maxLayerCount in xrEndFrame and validate quad extents
- Hook xrshim when scene_emul is enabled in adapter settings
- Update prebuilt native artifacts and checksums
2026-10-19 00:23:12 -04:00
spoopyghosty0 05344906ef MR diagnostics: log room-entity locates (base space, flags; every one without both valid bits): MRUK keeps an anchor pending forever otherwise (Pinball FX's load never succeeds) 2026-10-19 00:23:10 -04:00
spoopyghosty0 94ece1a232 MR scan on request (FrameBridge): a capture request asks FramePort to scan (request/ack/result files), holds the completion, reloads the scanned room on ok, fails with XR_ERROR_RUNTIME_FAILURE otherwise, falls back to the stored room without a scanner (setting scene_scan_request, default on) 2026-10-19 00:23:10 -04:00
spoopyghosty0 3298c5f64d MR: scene capture completes after 1.5 s like a real scan (Pinball FX re-asked at once); log each room query per entity and the labels an app gets (with its support info) 2026-10-19 00:19:18 -04:00
spoopyghosty0 087ad75f47 MR: shim-routed generic calls handle only fake room spaces and room events; real ones go to OVRPort's dispatcher (its headset identity, events and controller spaces were skipped for every scene_emul game: Vermillion's controllers, NOPE) 2026-10-19 00:19:18 -04:00
spoopyghosty0 460c503e43 MR diagnostics: log the head against the floor outline when a game looks for the room (MRUK's current room; Pinball FX asks for a scan again and again) 2026-10-19 00:19:17 -04:00
spoopyghosty0 3687a6eb0d MR: Meta scene/anchor permissions with every scene_emul recipe (migration scene_permissions; NOPE/Pinball never asked for the room), emulate XR_META_boundary_visibility, log the app's passthrough calls 2026-10-19 00:19:17 -04:00
spoopyghosty0 e07441fb09 MR: hiding the STAGE bounds becomes per-game setting scene_hide_bounds (Demeter on; NOPE stalled on a black screen without a play area) 2026-10-19 00:19:16 -04:00
spoopyghosty0 7971354675 MR: hide the STAGE bounds from scene-emulation apps (OVRPlugin can't outline the Frame's 1x1 m rect: Demeter's guardian check threw), box walls in loop order 2026-10-19 00:19:15 -04:00
spoopyghosty0 c3144f65dd MR: room entities offer SHARABLE like Meta's runtime; log refused scene components (Demeter rejected the room) 2026-10-19 00:19:15 -04:00
spoopyghosty0 210d6e80c5 MR: rooms older Meta SDKs accept (labels as Meta's runtime maps them, GLOBAL_MESH only with the mesh extension), rectangular rooms from perpendicular walls, ceilings found after a short look up, 60 s minimum scan 2026-10-19 00:19:15 -04:00
spoopyghosty0 e8eea8548c MR: scan the room in the headset - RoomView 3D capture with headset prompts, wall labels, agent 92
The agent records Valve's passthrough depth mesh (IVRBlockQueue + IVRPaths, Background/Overlay OpenVR app), converts
poses with the final raw->standing transform, shows pre-rendered prompts as a SteamVR overlay, then builds the room
with fp_roomscan (room_capture / room_capture_status). PC: rooms.scan, Frame page progress, frameport frame room scan.
FrameBridge reads the optional wall label (INVISIBLE_WALL_FACE). ROOM_FORMAT: outline orientation stated exactly.
2026-10-19 00:18:42 -04:00
spoopyghosty0 577b72969a Verify shim routing and protect erased or stale scene handles 2026-10-19 00:17:34 -04:00
spoopyghosty0 11ccdb7fac Test room geometry discovery persistence and malformed data on the host 2026-10-19 00:17:33 -04:00
spoopyghosty0 7fe604dbec Report scanned room or guardian box on scene capture requests 2026-10-19 00:17:33 -04:00
spoopyghosty0 2306b2e788 Route all emulated scene APIs and fake space consumers through the shim 2026-10-19 00:17:33 -04:00
spoopyghosty0 65b39a51ca Persist random UUID application anchors across sessions 2026-10-19 00:17:32 -04:00
spoopyghosty0 8473fe429e Add META discovery and persistence entry points and events 2026-10-19 00:17:32 -04:00
spoopyghosty0 3586d520d3 Expose room polygons volume bounds and triangle meshes 2026-10-19 00:17:32 -04:00
spoopyghosty0 79f60057fa Load scanned STAGE room files and bounded mesh sidecars 2026-10-19 00:17:31 -04:00
Codex 25db57f468 Fix #387: pool GLES panel swapchains before the runtime count limit
Add opt-in swapchain_pool with private GLES images and up to two packed
runtime atlases. Start at eight live app runtime swapchains to reserve
headroom before the session cap. Preserve image ownership, GL state,
source fences, crops and the existing cylinder/equirect layer ordering.

Enable for 4XVR, add count-limit triage without unknown suggestions for
older apps, and document the pending headset menu check. Leave default
recipes and adapter revisions unchanged. Rebuild arm64/arm32 artifacts.

Validation: 132 targeted tests passed (2 skipped), including the native
runtime/GL harness; Ruff and generated docs pass. Full host suite:
2095 passed, 137 skipped, 12 failures and 16 setup errors, all denied
socket creation in this sandbox. No headset validation or push.
2026-10-19 00:04:16 -04:00
spoopyghosty0 d0a2d89c92 Keep opt-in OpenXR layer recommendations at full swapchain resolution (#406)
Route OVRPlugin recommendations through the existing extension shim, retain native loader hooks, and log projection rect changes once per second per eye. Enable the fix for Batman, document verification, and rebuild both adapter architectures.
2026-10-19 00:03:20 -04:00
spoopyghosty0 d801ff6bc8 Controller buttons: second button up and toward the other hand like Quest (Quest pair: Y = d-pad right, B = X; Mirror follows); d-pad touch is one sensor 2026-10-18 09:23:52 -04:00
spoopyghosty0 33bb14e034 FrameBridge: cube-only frames as quads (#385); CI: lint, foveation test
HardBullet's loading frames submit only a cube layer, which the Frame's runtime doesn't composite, so the loading
screen stayed black. Adapter setting cube_quads (default on) draws a cube-only frame's six faces as quad layers
around the viewer; frames with a projection layer are unchanged. Adapter rebuilt on current main.

Also: the foveation test expected a numeric FOVE_LEVEL to pass through (Valve's layer ignores numbers since #394),
a long description line and an import order in adapter_dialog.py failed lint.
2026-10-17 21:52:39 -04:00
spoopyghosty0 b42d85d7c4 Fix Frame binding ownership and accepted Touch translation 2026-10-17 21:36:41 -04:00
spoopyghosty0 1f64d089e1 quest_buttons: bare trigger/squeeze/button paths map too (OpenXR resolves them to value/click) 2026-10-17 21:36:37 -04:00
spoopyghosty0 1f27cd47a3 Rebuild adapters and cover binding suggestion failures 2026-10-17 21:36:18 -04:00
spoopyghosty0 ff592e41e8 Add opt-in Quest controller bindings for Frame 2026-10-17 21:36:17 -04:00
spoopyghosty0 5e668f33d3 Batman audio hooks: place stubs in verified free gaps, say which step failed
On the Frame the atomic-branch detours never installed ("installation failed"): an mmap hint isn't honoured,
so the stub landed out of B range. Stubs now go into free gaps from /proc/self/maps within +-128 MiB of the
entry, mapped with MAP_FIXED_NOREPLACE (a relocated result is rejected); no gap = no hook. Every failure logs
the target and the failing step.
2026-10-17 16:47:06 -04:00
spoopyghosty0 feb4658dde Batman audio hooks: publish detours with one atomic branch (#389, #34)
The detour helper from PR #34 (used by the metadata hook and PR #389's two geometry hooks) wrote a 16-byte
branch + literal in two steps while game threads may run the function (installation is retried from
xrPollEvent). Each hook now publishes a single aligned release-atomic AArch64 B to a stub within +-128 MiB;
the trampoline replays the one verified instruction and returns to entry+4. Every mprotect is checked;
failures before publication free the stub and trampoline and leave the function untouched; no stub in range =
no hook. Adapter artifacts rebuilt by us (the PR's binary isn't shipped).
2026-10-17 16:47:06 -04:00
Lucas-Mathieu 78fc926a48 Fix Batman audio geometry input lifetime across background loading 2026-10-17 16:47:05 -04:00
spoopyghosty0 1e1c435191 Headset model per game, Quest 3 by default (#373)
Games saw a Quest 2: OVRPort's dispatcher hardcodes the system name "Oculus Quest2" and a Quest 2
headset UUID (XR_META_headset_id), and OVRPlugin decides the headset type from the UUID first, then the
name. device.headset_model (Customize; Quest 3 default for every Quest game, also Quest 3S, Quest Pro,
Quest 2) rewrites both in the dispatcher after checking the original bytes; Quest 2 leaves the build
byte-identical. FrameBridge reports the same name to games that call the runtime directly. A library
migration adds the default to existing recipes (installed games show Update on Frame).
Lepton's ro.product.model ("Lepton") can't be overridden by boot properties, so Build.MODEL is unchanged.

Verified on the Frame: OVRPlugin logs "systemName Meta Quest 3" (was "Oculus Quest2").
2026-10-17 15:18:45 -04:00
spoopyghosty0 7d4e20844f FrameBridge: XR_FB_color_space for games that require it (#336)
OutRun Quest VR quits before its VR session with "Rec.709 color space unavailable": the Frame's runtime
has no XR_FB_color_space. Adapter setting fb_color_space (off by default; triage color-space-missing
suggests it) offers the extension when the runtime lacks it, reports Rec.709 and accepts any colour
space without converting. OVRPort's dispatcher can't forward the new functions, so it isn't on for every
game. Not yet tried on the device.
2026-10-17 14:56:59 -04:00