Compare commits

...
Author SHA1 Message Date
spoopyghosty0 38eb90a0d6 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-18 12:29:29 -04:00
spoopyghosty0andClaude Opus 5.5 54effe9ab0 Catalog: HardBullet (issues: cube loading screen, view freezes during loads)
Closes #385

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:17:35 -04:00
spoopyghosty0andClaude Opus 5.5 d78fcbd20a OpenGL ES shader dump/fix offered for Unity games whose boot.config mentions Vulkan; either setting brings in the shader-fix layer (#382)
SynthRiders is analysed as 'Vulkan (Unity boot.config)' but renders with GLES on Zink, so the dump was hidden in
Game settings and session triage's suggestion was dropped; the dump setting alone also never added the layer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:14:42 -04:00
spoopyghosty0andClaude Opus 5.5 0322b8b4d6 Catalog: BattleTalent online rooms need Meta's account check, LAN works (#408)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:06:49 -04:00
24 changed files with 438 additions and 21 deletions

No files matched your search

+2 -2
View File
@@ -13,11 +13,11 @@ e32f9b3222bfe2e58c193db809ac2cf54086df36fd2ec6651aa2e58308f6b39d ./arm64-v8a/li
4a13a33e112c9e4e3c5ff4337ed6e988a9e5b3602f48d654df148ef5fafbf426 ./arm64-v8a/libfpglmv.so
36400eae4ba3807bcfcd54463daa217347032b07218792faa448d8cbbd086a0e ./arm64-v8a/libframe_xrshim.so
19ba5ed3c4b780f5ae46251f6958f552acfc5f13c8bb39f995579593fa8990c6 ./arm64-v8a/libglshim.so
f0894d9cc08aed4c0f6468134e76909d583a208c3f5e528bc3ee795f51b19fcb ./arm64-v8a/libopenxr_loader_generic.so
b085db012dfcdf2690fc3c53e8c3fef0c5d2011e35d4307756b0c6347e29d219 ./arm64-v8a/libopenxr_loader_generic.so
1feaeafad467c4cafdf2b018a4d84b0bee200c3f711697b4ce97e66a3ba256ca ./arm64-v8a/libovrplatformcompat.so
a4ed80ae45ca90758239f152ea5a2e8c127d491e1e697ea5680db6fd7f72db07 ./arm64-v8a/libvrapi.so
a4ed80ae45ca90758239f152ea5a2e8c127d491e1e697ea5680db6fd7f72db07 ./arm64-v8a/libvrapi_foveation.so
4e03a8389981ed5a27b4c159cc83168c4f93d0d75479a445b02e5669aa26a71b ./armeabi-v7a/libopenxr_loader_generic.so
fcd8f37b3acc3a2829f255c2f2fa16d20b975c8831d72518a659cb78ed6e3ae2 ./armeabi-v7a/libopenxr_loader_generic.so
1871eae093432d277da4bc751bf5f3269dfcf11f9168260b0c3caadb0dedb19d ./dex/oculusos-stubs.dex
b634ab5640e258563c536e658cad87080553df6f34f62269a21d554844e58bfe ./hevc/COPYING.FFmpeg
141de01f0d40b97d4f9a720db5ab8ea442f6aa35db5272048899292884994a1c ./hevc/libstagefrighthw.so
Binary file not shown.
Binary file not shown.
+2 -1
View File
@@ -2,7 +2,8 @@ package: com.CyDream.BattleTalent
title: BattleTalent
status: issues
notes: "Plays normally. The mixed-reality mode doesn't appear in the start menu (also with an APK from a Quest 3); MR
progress in #355. Multiplayer not tested yet."
progress in #355. LAN multiplayer works; online rooms need Meta's account check (user proof), so online
multiplayer can't work (#408)."
tested_version: '4.7'
engine: Unity
xr: OpenXR
@@ -0,0 +1,25 @@
package: com.GexagonVR.HardBullet
title: HardBullet
status: issues
notes: Loading screens (a cube overlay) are missing or corrupted up to FramePort 1.2.1; cube_quads (next release)
shows them as six panels, not confirmed in the headset yet. Level loads stall frame submission for up to ~1 s, so
the view freezes during loads.
tested_version: 1.0.9.704Q
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.hw_video_decode
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-17'
known_good_sha256: ecbb758d98083a590f100d5ac6efe9490ef74ad39b59217e0b4a11db920ecd51
app: 1.2.1
overport_cli: 1.2.5
frame_build: '20261015.6141207'
agent: 96
issue: 385
source_hint: HARD BULLET
+3 -2
View File
@@ -16,6 +16,7 @@ frame:
- frame.hw_video_decode
- frame.unity_text_input
adapter:
fixed_resolution: 1
sync_guard: 1
surface_native: 1
fade_quad: 1
@@ -26,6 +27,6 @@ verified:
overport_cli: 1.2.3
overport_runtime: 3.4.3-23204ea
known_good_sha256: 796d1cab08f614d72c50d9702974c719c0908f5de91183cfe02d1fd5eb09551a
updated: '2026-10-15'
min_app: 1.1.0
updated: '2026-10-18'
min_app: 1.2.2
source_hint: Batman- Arkham Shadow
+7
View File
@@ -6,6 +6,13 @@
# with diagnostics. Launch tests and real play sessions (agent session_log, `frameport session`) are both matched.
# Sources: every failure we hit porting 34 games to the Steam Frame (see docs/PLAYBOOK.md).
signatures:
- id: dynamic-resolution-foveation
pattern: 'projection view [01](?: rect change)?:[^\r\n]*rect=(?:[1-9]\d*,\d+|0,[1-9]\d*) '
unless: 'fixed_resolution: recommended'
severity: info
diagnosis: The game submits an offset projection viewport. With Valve foveation, dynamic resolution can move the sharp centre or expose a white edge. Keeping full resolution may help; it increases GPU load and needs the updated adapter and OVRPlugin shim in a rebuilt APK.
question: With foveated rendering enabled, was the sharp centre misplaced or was there a white line at the picture edge?
suggest: [adapter.fixed_resolution]
- id: quest2-headset-identity
pattern: 'venderId[^\r\n]*systemName Oculus Quest2'
severity: info
+42
View File
@@ -785,3 +785,45 @@ SteamVR's own Touch remap (`/opt/steamvr/drivers/frame_controller/resources/inpu
layout `oculus_touch`) is the diagram's "SteamVR default": X -> d-pad down, Y -> d-pad left/right/up, A -> A,
B -> B/X/Y, Menu -> View, plus trigger click -> bumper. Games with their own Valve binding file in that folder
(Beat Saber, Batman, Accounting+, In Death, ...) use that file instead.
## Fixed layer resolution / Batman (#406, #394)
Batman: Arkham Shadow (`com.camouflaj.manta`, Unity + OVRPlugin, OpenXR/Vulkan) submits
`2768x2768`, then corner-anchored `rect=0,32 2736x2736`, `rect=0,64 2704x2704` inside the
same swapchain. On SteamOS 0.5.6 Valve's FDM injection covers the full swapchain: shrinking
viewports can move the sharp centre towards the top right and expose a coarse white right edge.
The runtime recommendation driving OVRPlugin's dynamic resolution remains unverified.
`adapter.fixed_resolution=1` (default off, Batman recipe on) overrides
`xrGetRecommendedLayerResolutionMETA`: tracked full swapchain dimensions, `isValid=XR_TRUE`.
The existing rect_clamp size table already records successful xrCreateSwapchain calls and
retires entries at destruction. Projection views must have matching sizes; quads are supported;
unknown swapchains, mixed sizes and other layer types retain the runtime behavior. Submitted
rectangles are never enlarged after rendering. Full resolution may increase GPU load.
The cached OVRPort 3.4.3-aa54c3f dispatcher contains both the extension and function name
(and a runtime-missing diagnostic). Its fixed routing table is not a guarantee that every
version forwards the call unchanged. Enable the existing arm64 xrshim for this setting:
OVRPlugin's loader lookup reaches `framebridge_extension_proc` directly, while native OpenXR
loaders use the adapter's xrGetInstanceProcAddr hook. No extension hiding: keeping the
recommendation API available avoids relying on an unverified OVRPlugin fallback. The adapter
revision stays unchanged because the new setting is opt-in; the catalog requires 1.2.2 so older
remote clients keep their bundled recipe (`recommend.catalog.unusable_reason`). Rebuild and
reinstall Batman to receive the binary and shim routing; this is not a settings-only fix for
an old APK. Device/headset verification is pending.
Headless check: expect `extension shim: xrGetRecommendedLayerResolutionMETA -> FrameBridge`
for OVRPlugin, then up to five `fixed_resolution: recommended WxH -> 2768x2768 (runtime=...)`
lines. Compare the size to the actual eye swapchain creation log; scaling can change it.
`projection view 0 rect change: ... rect=0,0 2768x2768` (and view 1) should remain full size
through gameplay/loading transitions. Rect changes continue to log at most once per second
per eye, comparing against the last logged state so a throttled final change is still shown.
No recommendation logs means this API path was not exercised: do not claim the fix worked.
Headset check: sharp region centred in both eyes, no white right edge, correct menus/loading
fades, and acceptable pacing with full-resolution GPU load.
This is separate from #303's `foveation_storage_fix` (clearing SUBSAMPLED allocation flags;
see PLAYBOOK and FRAME_RUNTIME), which fixes black/coarse storage corruption. It also leaves
#394's `FOVE_LEVEL=lo/med/hi` presets intact (commits 01ad5e6b, d7f0d514): those select the
full-swapchain density pattern, not the game's dynamic viewport.
+2
View File
@@ -143,6 +143,7 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
| Table Troopers | Quest | ✅ Works | |
| Taskmaster | Quest | ✅ Works | |
| Tea For God | Quest | ✅ Works | |
| Test Quest Controllers | Quest | ✅ Works | |
| TetrisEffect | Quest | ✅ Works | |
| The Boys VR | Quest | ✅ Works | |
| The Climb | Quest | ✅ Works | |
@@ -184,6 +185,7 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
| ExploreVR | Quest | ⚠️ Works with issues | The 3D world warps and shifts with head movement (wrong perspective). |
| FGO VR | Quest | ⚠️ Works with issues | Audio stuttering and popping sounds |
| Fisherman | Quest | ⚠️ Works with issues | Loading screens have corruption, but everything works otherwise. |
| HardBullet | Quest | ⚠️ Works with issues | Loading screens (a cube overlay) are missing or corrupted up to FramePort 1.2.1; cube_quads (next release) shows them as six panels, not confirmed in the hea… |
| Homeworld: Vast Reaches | Quest | ⚠️ Works with issues | The game starts but after selecting to start a new game it seems to crash. |
| Lawn Mowing Simulator VR | Quest | ⚠️ Works with issues | Left eye textures flicker only when mowing in specific areas. |
| MacacoVR | Quest | ⚠️ Works with issues | 2d loading screens are glitchy, otherwise main gameplay is fine |
+29 -11
View File
@@ -2,7 +2,7 @@
# Patches
Every patch and game setting FramePort knows: 186 entries in 5 groups. A game's recipe is its chosen patches and settings; tested games get theirs from the catalog ([GAMES.md](GAMES.md)), and other games get the patches FramePort suggests from its analysis. You change them on the game page under Customize.
Every patch and game setting FramePort knows: 187 entries in 5 groups. A game's recipe is its chosen patches and settings; tested games get theirs from the catalog ([GAMES.md](GAMES.md)), and other games get the patches FramePort suggests from its analysis. You change them on the game page under Customize.
Each entry says what the patch does, whether it changes the APK or only the install, when FramePort suggests it, its options, the catalog games whose recipe names it and the launch-test signatures that suggest it. Patches that are on by default are on for every game, so the catalog rarely names them.
@@ -12,7 +12,7 @@ Link to an entry with its id, dots replaced by dashes: `PATCHES.md#frame-unity_t
- [Patches from OVRPort](#group-overport) (25)
- [Steam Frame patches](#group-frame) (61)
- [Game settings](#group-adapter) (77)
- [Game settings](#group-adapter) (78)
- [Files and settings on the Frame](#group-device) (10)
- [PC VR (Proton and Revive)](#group-pcvr) (13)
@@ -58,7 +58,7 @@ Link to an entry with its id, dots replaced by dashes: `PATCHES.md#frame-unity_t
| [GL shim (Mesa GLSL compatibility)](#frame-gl_shim) | `frame.gl_shim` | Steam Frame patches | 2 |
| [Play services: Android 11 version checks](#frame-gms_platform_version) | `frame.gms_platform_version` | Steam Frame patches | 1 |
| [Godot apps: start without a clipboard service](#frame-godot_clipboard) | `frame.godot_clipboard` | Steam Frame patches | 1 |
| [Hardware video decoding (H.264, HEVC, VP9)](#frame-hw_video_decode) | `frame.hw_video_decode` | Steam Frame patches | 33 |
| [Hardware video decoding (H.264, HEVC, VP9)](#frame-hw_video_decode) | `frame.hw_video_decode` | Steam Frame patches | 34 |
| [Language packs from the game's files](#frame-langpacks) | `frame.langpacks` | Steam Frame patches | |
| [Launcher category (INFO → LAUNCHER)](#frame-launcher) | `frame.launcher` | Steam Frame patches | default |
| [GL wrapper: OpenGL ES depth formats (Minecraft)](#frame-ltw_depth) | `frame.ltw_depth` | Steam Frame patches | default |
@@ -86,7 +86,7 @@ Link to an entry with its id, dots replaced by dashes: `PATCHES.md#frame-unity_t
| [Unity: skip OVROverlay layers (fades, splash screens)](#frame-unity_no_overlay_copy) | `frame.unity_no_overlay_copy` | Steam Frame patches | 2 |
| [Unity: start VR without Meta's system apps](#frame-unity_oculus_check) | `frame.unity_oculus_check` | Steam Frame patches | 14 |
| [Unity: keep MSAA off at runtime (OVRManager)](#frame-unity_runtime_msaa_off) | `frame.unity_runtime_msaa_off` | Steam Frame patches | 32 |
| [Make Unity text fields work](#frame-unity_text_input) | `frame.unity_text_input` | Steam Frame patches | 117 |
| [Make Unity text fields work](#frame-unity_text_input) | `frame.unity_text_input` | Steam Frame patches | 118 |
| [Unity: share thread keys (games that run out of them)](#frame-unity_tls_key_pool) | `frame.unity_tls_key_pool` | Steam Frame patches | 1 |
| [Unity: headset always counts as worn](#frame-unity_user_presence) | `frame.unity_user_presence` | Steam Frame patches | 1 |
| [GL shim for Unreal (no multisampled render-to-texture)](#frame-unreal_gl_shim) | `frame.unreal_gl_shim` | Steam Frame patches | 1 |
@@ -125,6 +125,7 @@ Link to an entry with its id, dots replaced by dashes: `PATCHES.md#frame-unity_t
| [Per-eye diagnostics (log)](#adapter-eye_debug) | `adapter.eye_debug` | Game settings | |
| [Draw fades over the picture](#adapter-fade_quad) | `adapter.fade_quad` | Game settings | 2 |
| [Colour space support](#adapter-fb_color_space) | `adapter.fb_color_space` | Game settings | |
| [Keep full resolution (no dynamic resolution)](#adapter-fixed_resolution) | `adapter.fixed_resolution` | Game settings | 1 |
| [Upside-down panel images (diagnostics)](#adapter-flip_dump) | `adapter.flip_dump` | Game settings | |
| [Fix upside-down menus](#adapter-flip_emul) | `adapter.flip_emul` | Game settings | |
| [Rotate panels 180°](#adapter-flip_quads) | `adapter.flip_quads` | Game settings | |
@@ -137,7 +138,7 @@ Link to an entry with its id, dots replaced by dashes: `PATCHES.md#frame-unity_t
| [GL shim: high-precision vertex shaders](#adapter-gl_highp_markers) | `adapter.gl_highp_markers` | Game settings | 1 |
| [GL shim: integer vertex inputs](#adapter-gl_int_attribs) | `adapter.gl_int_attribs` | Game settings | 1 |
| [Flat panel diagnostics (log)](#adapter-gl_mv_debug) | `adapter.gl_mv_debug` | Game settings | |
| [Fix freezes when controllers vibrate](#adapter-haptic_fix) | `adapter.haptic_fix` | Game settings | 102 |
| [Fix freezes when controllers vibrate](#adapter-haptic_fix) | `adapter.haptic_fix` | Game settings | 103 |
| [Vibration strength](#adapter-haptic_scale) | `adapter.haptic_scale` | Game settings | |
| [Turn off Quest Vulkan foveation](#adapter-hide_fb_foveation) | `adapter.hide_fb_foveation` | Game settings | |
| [Turn off space warp](#adapter-hide_space_warp) | `adapter.hide_space_warp` | Game settings | 5 |
@@ -192,7 +193,7 @@ Link to an entry with its id, dots replaced by dashes: `PATCHES.md#frame-unity_t
| [Game language](#device-language) | `device.language` | Files and settings on the Frame | |
| [Extra Lepton settings](#device-lepton_env) | `device.lepton_env` | Files and settings on the Frame | 3 |
| [Controllers as a gamepad (Steam Input)](#device-steam_gamepad) | `device.steam_gamepad` | Files and settings on the Frame | |
| [Show the app's Android window (for typing)](#device-text_input_window) | `device.text_input_window` | Files and settings on the Frame | 120 |
| [Show the app's Android window (for typing)](#device-text_input_window) | `device.text_input_window` | Files and settings on the Frame | 121 |
| [Launch options (SteamVR or OpenXR)](#pcvr-launch_args) | `pcvr.launch_args` | PC VR (Proton and Revive) | 2 |
| [Provide the Oculus runtime from Revive (Frame)](#pcvr-libovr_redirect) | `pcvr.libovr_redirect` | PC VR (Proton and Revive) | |
| [No Unreal crash reporter](#pcvr-no_crash_reporter) | `pcvr.no_crash_reporter` | PC VR (Proton and Revive) | |
@@ -693,7 +694,7 @@ Plays the game's videos on the Frame's video hardware, so 4K and 8K video runs s
- `frame.hw_video_decode` · also for 2D apps
- **Changes:** Install only: written on the Frame when the game is installed; the APK isn't changed.
- **Suggested (on):** The game plays video through Android's decoders (MediaCodec, ExoPlayer or VLC): decode it on the Frame's hardware.
- **Catalog games (33):** 4XVR Video Player, AugmentedEmpire, Batman: Arkham Shadow, Battlemarked, BattleTalent, Breathedge VR, Dance Central, Exploding Kittens VR and 25 more.
- **Catalog games (34):** 4XVR Video Player, AugmentedEmpire, Batman: Arkham Shadow, Battlemarked, BattleTalent, Breathedge VR, Dance Central, Exploding Kittens VR and 26 more.
- **Source:** [patches/frame/hw_video_decode.py](../src/frameport/patches/frame/hw_video_decode.py)
> Decodes the game's videos on the Frame's hardware video decoder instead of Android's software decoders, which stutter or fall behind with 4K/8K video (for example video players, Batman: Arkham Shadow's cutscenes). Adds FramePort's codec plugin (OMX.frameport.avc/hevc/vp9.decoder) to this game's container only; the APK isn't changed. When the hardware is busy (for example Steam's own hardware video decoding is on) the video is decoded in software as before.
@@ -1066,7 +1067,7 @@ Lets you type into this app's text fields on the Frame (they'd close at once).
- `frame.unity_text_input` · also for 2D apps
- **Changes:** Changes the APK (a rebuild and reinstall apply it).
- **Suggested (on):** Unity app with text fields (…): they close at once on the Frame because it has no system keyboard.
- **Catalog games (117):** AgeOfJoy, AllInOneSports, Ancient_Dungeon, Arcsmith, Arkio, Assassin's Creed Nexus, Attack on Titan VR: Unbreakable, Audica and 109 more.
- **Catalog games (118):** AgeOfJoy, AllInOneSports, Ancient_Dungeon, Arcsmith, Arkio, Assassin's Creed Nexus, Attack on Titan VR: Unbreakable, Audica and 110 more.
- **Source:** [patches/frame/unity_text_input.py](../src/frameport/patches/frame/unity_text_input.py)
> Unity's TMP_InputField / InputField wait for Android's on-screen keyboard and close themselves a frame later when there is none (Lepton has no on-screen keyboard; Meta's system keyboard does this on a Quest), so a selected text field only flashes a caret. Rewrites TouchScreenKeyboardShouldBeUsed -&gt; false and isKeyboardUsingEvents (TMP) / InPlaceEditing (uGUI) -&gt; true in libil2cpp.so, found per game with Cpp2IL (downloaded on first use), so fields stay selected and take key presses: Steam's keyboard (with "Show the app window") or Type on Frame from the PC.
@@ -1325,7 +1326,7 @@ Repairs shaders that hang the graphics chip at certain effects, from the game's
- **Catalog games (2):** Star Wars: Tales from the Galaxy's Edge, Vader Immortal: Episode I.
- **Source:** [patches/frame/zink_shader_fix.py](../src/frameport/patches/frame/zink_shader_fix.py)
> Loads FramePort's shader-fix Vulkan layer into an OpenGL ES game. On the Frame, OpenGL ES runs on Zink (Mesa's GL on Vulkan); the layer sits between Zink and the driver and inserts the words of the adapter setting zink_shader_fix into the SPIR-V modules whose size and SHA-256 match (every other module is untouched), for example stores that set loop counters and accumulators a shader reads before writing, which hang the GPU (Vader Immortal's lightspeed jump; found by Klownicle, GitHub #49). Does nothing without zink_shader_fix (or zink_shader_dump) in the recipe. The layer adds itself to Android's Vulkan debug layer list for the game's process only.
> Loads FramePort's shader-fix Vulkan layer into an OpenGL ES game. On the Frame, OpenGL ES runs on Zink (Mesa's GL on Vulkan); the layer sits between Zink and the driver and inserts the words of the adapter setting zink_shader_fix into the SPIR-V modules whose size and SHA-256 match (every other module is untouched), for example stores that set loop counters and accumulators a shader reads before writing, which hang the GPU (Vader Immortal's lightspeed jump; found by Klownicle, GitHub #49). Does nothing without zink_shader_fix (or zink_shader_dump) in the recipe, and either setting brings it in by itself. The layer adds itself to Android's Vulkan debug layer list for the game's process only.
## <a id="group-adapter"></a>Game settings
@@ -1607,6 +1608,23 @@ For games that stop at start because the headset reports no colour space (for ex
> Offers Meta's colour-space extension when the Frame's runtime lacks it: the game is told the headset uses Rec.709 and may choose any colour space (no conversion is done). For games that quit before their VR session with "Rec.709 color space unavailable" (for example OutRun Quest VR, GitHub #336). Only reaches games that use their own OpenXR loader; OVRPort's dispatcher can't forward the new functions.
### <a id="adapter-fixed_resolution"></a>Keep full resolution (no dynamic resolution)
Prevents dynamic resolution from moving Valve foveation off-centre or leaving a white edge line. May increase GPU load. Used at the next launch.
- `adapter.fixed_resolution`
- **Changes:** Game setting: stored in the build and on the Frame (settings.conf); can be changed without a rebuild.
- **Suggested:** not automatically; set on the game page (Customize), by a recipe or after a launch test.
- **Options:**
- Value: int, default `0`.
- Game settings → Picture → "Keep full resolution (no dynamic resolution)" (advanced).
- **Catalog games (1):** Batman: Arkham Shadow.
- **Suggested by launch-test signatures:**
- `dynamic-resolution-foveation` (info): The game submits an offset projection viewport.
- **Source:** [patches/settings.py](../src/frameport/patches/settings.py)
> Keeps OpenXR layer recommendations at the full swapchain size. Prevents dynamic resolution from moving Valve foveation off-centre or leaving a coarse white edge line. May increase GPU load.
### <a id="adapter-flip_dump"></a>Upside-down panel images (diagnostics)
Saves a few UI panels before and after FrameBridge turns them upright, for testing without the headset.
@@ -1794,7 +1812,7 @@ For games that freeze the Frame when a controller vibrates (for example Lucky's
- **Options:**
- Value: int, default `0`.
- Game settings → Controllers → "Fix freezes when controllers vibrate" (advanced).
- **Catalog games (102):** AgeOfJoy, Ancient_Dungeon, Attack on Titan VR: Unbreakable, Audica, AugmentedEmpire, Battlemarked, BattleSisters, BattleTalent and 94 more.
- **Catalog games (103):** AgeOfJoy, Ancient_Dungeon, Attack on Titan VR: Unbreakable, Audica, AugmentedEmpire, Battlemarked, BattleSisters, BattleTalent and 95 more.
- **Source:** [patches/settings.py](../src/frameport/patches/settings.py)
> Routes OVRPlugin through FramePort's extension shim, which turns Meta's amplitude-envelope vibrations into plain ones: OVRPort's loader reads their nanosecond duration as seconds and allocates gigabytes, freezing the Frame when the game vibrates a controller (for example Lucky's Tale's save slots, GitHub #9). Needs a rebuild.
@@ -2601,7 +2619,7 @@ Lets typing reach this app from Steam's on-screen keyboard or your PC's keyboard
- `device.text_input_window`
- **Changes:** Install only: written on the Frame when the game is installed; the APK isn't changed.
- **Suggested (on):** App with text fields: key presses only reach a shown Android window.
- **Catalog games (120):** AFFECTED: The Manor, AgeOfJoy, AllInOneSports, Ancient_Dungeon, Arcsmith, Arkio, Assassin's Creed Nexus, Attack on Titan VR: Unbreakable and 112 more.
- **Catalog games (121):** AFFECTED: The Manor, AgeOfJoy, AllInOneSports, Ancient_Dungeon, Arcsmith, Arkio, Assassin's Creed Nexus, Attack on Titan VR: Unbreakable and 113 more.
- **Source:** [patches/settings.py](../src/frameport/patches/settings.py)
> Lepton runs VR apps headless: their Android window is never shown, so it never gets keyboard focus and no key press (Steam's on-screen keyboard, a USB/Bluetooth keyboard, Type on Frame) reaches the app's text fields. With Lepton's lepton-show-flatscreen marker the window is shown (behind the VR view, which keeps working) and Steam's keyboard opens for a selected text field. Steam lists the shown window as "Gamescope" (the Frame's compositor). Pairs with "Make Unity text fields work without a system keyboard".
+1
View File
@@ -13,6 +13,7 @@ version is `catalog/triage.yaml` (used by `frameport test` / the Job screen); ke
## Startup failures (visible in launch.log)
| Symptom | Cause | Fix |
|---|---|---|
| Valve foveation sharp centre moves to the top right or a white line appears at the right edge as Batman shrinks its projection rect (#406 / #394) | Dynamic resolution draws a corner-anchored subrect inside the full swapchain used by Valve FDM | Enable `adapter.fixed_resolution` (Game settings → Picture), rebuild/reinstall for the new adapter and OVRPlugin shim; verify `fixed_resolution: recommended ... -> ...` and full-size `projection view ... rect change` logs. Headset verification pending; more GPU load |
| Quest 3-only options missing; OVRPlugin logs `venderId 10291, systemName Oculus Quest2` (#373, Asgard's Wrath 2) | OVRPort's dispatcher overwrites FrameBridge's name and supplies a Quest 2 `XR_META_headset_id`; OVRPlugin prioritizes that UUID | `device.headset_model`: Quest 3 by default, per-game Quest 3S / Quest Pro / Quest 2; rebuild the APK. Byte-checked dispatcher name + UUID edit; Quest 2 leaves the dispatcher unchanged. See DEVICE_NOTES 'Per-game headset identity' |
| Startup exits through `libxlua.so (lj_err_throw)`, then Unity aborts in mutex cleanup; captured Lua error is `bad light userdata pointer` (GitHub #257, BattleTalent 4.7) | This older LuaJIT encodes lightuserdata in 47 bits; Frame supplies valid wider native pointers. Controlled tests with the unchanged library pass a real low-address allocation and fail its own high-address static pointer | `frame.luajit_pointers` automatically selects a full-pointer Lua engine only for the verified library SHA-256, retaining xLua bindings and custom helpers. The scoped patch reaches OpenXR startup for 60 s headless; rendering and gameplay remain unverified. A `lj_err_throw` cleanup stack without the original error message is insufficient to infer this cause |
| A GL4ES Team Beef game draws stretched triangles or spikes across characters and the scene (GitHub #191, Lambda1VR) | The newer GL shim exports GLES draw functions that intercept GL4ES's desktop OpenGL calls, bypassing its client-array and immediate-mode translation | `frame.tbxr_vendor` keeps the shim's five draw symbols private when the APK bundles `libgl4es.so`. GL queries and single-sampled framebuffer fixes remain active; other games retain the draw hooks. A/B eye captures on SteamOS 0.4.5: old shim normal, current shim spikes, current shim with private draw symbols normal at ~72 fps. Worn-headset gameplay still needs confirmation |
+6
View File
@@ -31,3 +31,9 @@ missing symbols (no compiler needed).
After changing anything here: `python native/build.py`, commit artifacts + SHA256SUMS, then run `frameport parity`
(the report marks updated binaries as "expected") and a device launch test.
`adapter.fixed_resolution` (off by default, #406/#394) keeps META recommended layer
resolution at tracked full swapchain size. Both adapter GIPA and xrshim's existing
extension entry point serve it; the setting adds OVRPlugin shim routing. The extension
stays visible, avoiding dependence on a plugin fallback. No adapter revision bump for
this opt-in fix. See DEVICE_NOTES "Fixed layer resolution / Batman" for verification.
+22
View File
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-3.0-only
// swapchain_size is the adapter's mutex-protected table, shared with rect_clamp.
static int fixed_layer_size(const XrCompositionLayerBaseHeader *layer, XrExtent2Di *full) {
if (!layer) return 0;
uint32_t w = 0, h = 0;
if (layer->type == XR_TYPE_COMPOSITION_LAYER_PROJECTION) {
const XrCompositionLayerProjection *p = (const XrCompositionLayerProjection *)layer;
if (!p->viewCount || !p->views) return 0;
for (uint32_t v = 0; v < p->viewCount; ++v) {
uint32_t vw, vh;
if (!swapchain_size(p->views[v].subImage.swapchain, &vw, &vh)) return 0;
if (v && (vw != w || vh != h)) return 0; // no single full size for this layer
w = vw; h = vh;
}
} else if (layer->type == XR_TYPE_COMPOSITION_LAYER_QUAD) {
const XrCompositionLayerQuad *q = (const XrCompositionLayerQuad *)layer;
if (!swapchain_size(q->subImage.swapchain, &w, &h)) return 0;
} else return 0; // preserve runtime behavior for other layer types
if (!w || !h || w > INT32_MAX || h > INT32_MAX) return 0;
full->width = (int32_t)w; full->height = (int32_t)h;
return 1;
}
+56
View File
@@ -131,6 +131,7 @@ static int swapchain_fix = 1;
static int cube_quads = 1;
static int cube_standin = 1; // cube swapchains the runtime refuses: served by the adapter (cube_standin.c)
static int rect_clamp = 1;
static int fixed_resolution;
static int fb_color_space; // fb_color_space: offer XR_FB_color_space when the runtime lacks it (#336)
static int action_dedup = 1; // rename duplicate action names in a set (session_fixes.c; GitHub #323)
static int pose_consistency = 0; // repeat xrLocateViews queries for one display time get the same poses (GitHub #8) // clamp submitted image rects to their swapchain (SteamVR rejects a 1 px overrun)
@@ -207,6 +208,7 @@ static void read_settings(const char *path) {
if (sscanf(line, "swapchain_fix=%f", &value) == 1) swapchain_fix = value != 0;
if (sscanf(line, "cube_quads=%f", &value) == 1) cube_quads = value != 0;
if (sscanf(line, "cube_standin=%f", &value) == 1) cube_standin = value != 0;
if (sscanf(line, "fixed_resolution=%f", &value) == 1) fixed_resolution = value != 0;
if (sscanf(line, "rect_clamp=%f", &value) == 1) rect_clamp = value != 0;
if (sscanf(line, "fb_color_space=%f", &value) == 1) fb_color_space = value != 0;
if (sscanf(line, "action_dedup=%f", &value) == 1) action_dedup = value != 0;
@@ -298,6 +300,7 @@ static void initialize(void) {
if (input_diag) LOG("per-game: input_diag=1 (controller-input diagnostics)");
if (proximity_emul) LOG("per-game: proximity_emul=%d (finger proximity from capacitive touch: %s)", proximity_emul,
proximity_emul > 1 ? "thumb + index" : "thumb");
if (fixed_resolution) LOG("per-game: fixed_resolution=1 (full swapchain recommendations)");
if (hide_fb_foveation) LOG("per-game: hide_fb_foveation=1 (Quest Vulkan foveation suppressed)");
if (hide_space_warp) LOG("per-game: hide_space_warp=1 (XR_FB_space_warp hidden, space warp info removed)");
if (strip_color_bias) LOG("per-game: strip_color_bias=%d (layer color scale/bias removed)", strip_color_bias);
@@ -595,6 +598,29 @@ static int swapchain_size(XrSwapchain handle, uint32_t *w, uint32_t *h) {
return found;
}
#include "fixed_resolution.h"
static XRAPI_ATTR XrResult XRAPI_CALL fixed_recommended_resolution(XrSession session,
const XrRecommendedLayerResolutionGetInfoMETA *info, XrRecommendedLayerResolutionMETA *out) {
PFN_xrGetRecommendedLayerResolutionMETA fn = (PFN_xrGetRecommendedLayerResolutionMETA)
lookup(active_instance, "xrGetRecommendedLayerResolutionMETA");
if (!info || !out) return XR_ERROR_VALIDATION_FAILURE;
XrExtent2Di full;
if (!fixed_layer_size(info->layer, &full))
return fn ? fn(session, info, out) : XR_ERROR_FUNCTION_UNSUPPORTED;
// Read the runtime recommendation only for diagnostics. The override also works
// when the runtime lacks this function (OVRPort advertises it itself).
XrRecommendedLayerResolutionMETA old = {XR_TYPE_RECOMMENDED_LAYER_RESOLUTION_META};
XrResult result = fn ? fn(session, info, &old) : XR_ERROR_FUNCTION_UNSUPPORTED;
static unsigned logged;
if (logged++ < 5) LOG("fixed_resolution: recommended %dx%d -> %dx%d (runtime=%d)",
old.recommendedImageDimensions.width, old.recommendedImageDimensions.height,
full.width, full.height, result);
out->recommendedImageDimensions = full;
out->isValid = XR_TRUE;
return XR_SUCCESS;
}
// Clamp `r` to a w x h image; returns whether it changed (2 = it was empty and now covers the whole image).
static int clamp_rect(XrRect2Di *r, uint32_t w, uint32_t h) {
XrRect2Di c = *r;
@@ -2064,6 +2090,30 @@ XRAPI_ATTR XrResult XRAPI_CALL xrEndFrame(XrSession session, const XrFrameEndInf
static int warned, failures, passthrough_logged;
if (dropped && warned++ < 5) LOG("xrEndFrame: dropped %u unusable layer(s)", dropped);
if (passthrough && !passthrough_logged++) LOG("xrEndFrame: passthrough layer -> ALPHA_BLEND environment");
static struct { XrSwapchain swapchain; XrRect2Di rect; uint32_t array; } last_rect[2];
static double last_rect_log[2];
struct timespec rect_now;
clock_gettime(CLOCK_MONOTONIC, &rect_now);
double rect_seconds = rect_now.tv_sec + rect_now.tv_nsec / 1e9;
for (uint32_t i = 0; i < info->layerCount; ++i) {
const XrCompositionLayerBaseHeader *l = info->layers[i];
if (!l || l->type != XR_TYPE_COMPOSITION_LAYER_PROJECTION) continue;
const XrCompositionLayerProjection *p = (const XrCompositionLayerProjection *)l;
for (uint32_t v = 0; v < p->viewCount && v < 2; ++v) {
const XrSwapchainSubImage *sub = &p->views[v].subImage;
if ((last_rect[v].swapchain != sub->swapchain || last_rect[v].array != sub->imageArrayIndex ||
memcmp(&last_rect[v].rect, &sub->imageRect, sizeof(sub->imageRect))) &&
rect_seconds - last_rect_log[v] >= 1.0) {
LOG("projection view %u rect change: swapchain=%p rect=%d,%d %dx%d array=%u", v,
(void *)(uintptr_t)sub->swapchain, sub->imageRect.offset.x, sub->imageRect.offset.y,
sub->imageRect.extent.width, sub->imageRect.extent.height, sub->imageArrayIndex);
last_rect[v].swapchain = sub->swapchain;
last_rect[v].rect = sub->imageRect;
last_rect[v].array = sub->imageArrayIndex;
last_rect_log[v] = rect_seconds;
}
}
}
static int views_logged;
if (views_logged < 3) { // describe the stereo layer layout a few times to debug eye/stereo problems
for (uint32_t i = 0; i < info->layerCount; ++i) {
@@ -2250,6 +2300,8 @@ static XRAPI_ATTR XrResult XRAPI_CALL fb_hide_update_swapchain(XrSwapchain swapc
// For native/xrshim: emulated functions that overport's dispatcher doesn't know (it never asks us for them).
__attribute__((visibility("default"))) PFN_xrVoidFunction framebridge_extension_proc(const char *name) {
pthread_once(&init_once, initialize);
if (fixed_resolution && name && !strcmp(name, "xrGetRecommendedLayerResolutionMETA"))
return (PFN_xrVoidFunction)fixed_recommended_resolution;
if (name && !strcmp(name, "framebridge_hide_fb_foveation"))
return (PFN_xrVoidFunction)framebridge_hide_fb_foveation;
if (hide_fb_foveation_vulkan && name) {
@@ -2302,6 +2354,10 @@ XRAPI_ATTR XrResult XRAPI_CALL xrGetInstanceProcAddr(XrInstance instance, const
*function = (PFN_xrVoidFunction)fb_hide_update_swapchain;
return XR_SUCCESS;
}
if (fixed_resolution && !strcmp(name, "xrGetRecommendedLayerResolutionMETA")) {
*function = (PFN_xrVoidFunction)fixed_recommended_resolution;
return XR_SUCCESS;
}
HOOK(xrGetSystemProperties)
HOOK(xrApplyHapticFeedback)
HOOK(xrCreateReferenceSpace)
+5
View File
@@ -94,6 +94,11 @@
"title": "Colour space support",
"group": "Game settings"
},
"adapter.fixed_resolution": {
"anchor": "adapter-fixed_resolution",
"title": "Keep full resolution (no dynamic resolution)",
"group": "Game settings"
},
"adapter.flip_dump": {
"anchor": "adapter-flip_dump",
"title": "Upside-down panel images (diagnostics)",
+4
View File
@@ -11,6 +11,7 @@ from .core.events import Reporter
from .core.models import Analysis, BuildResult, Recipe, SourceGame
from .core.paths import work_dir
from .patches import base, upstream
from .patches.frame import zink_shader_fix
from .patches.overport import OVERPORT_PATCHES
from .tools import overport as overport_tool
from .validate.static import check_apk
@@ -54,6 +55,9 @@ def apk_patches(selection: dict) -> list[base.Patch]:
if "frame.adapter" in selection and "device.foveation" not in selection:
# Prepare verified game-specific support independently of the player's runtime preference.
patches.append(base.get("device.foveation"))
if zink_shader_fix.configured(selection) and "frame.zink_shader_fix" not in selection:
# the shader dump / fix settings only work through this layer (GitHub #382: the switch alone did nothing)
patches.append(base.get("frame.zink_shader_fix"))
return sorted(patches, key=lambda p: p.order)
+6
View File
@@ -20,6 +20,12 @@ def is_gles(a: Analysis) -> bool:
return "GLES" in a.graphics
def may_be_gles(a: Analysis) -> bool:
"""GLES, or Unity whose boot.config merely mentions Vulkan: many such builds render with OpenGL ES (SynthRiders,
I Am Cat; GitHub #382), so GLES-only diagnostics and fixes stay offered."""
return is_gles(a) or a.graphics == "Vulkan (Unity boot.config)"
def is_vulkan(a: Analysis) -> bool:
return a.graphics.startswith("Vulkan")
@@ -10,6 +10,7 @@ are set hang the GPU) by Klownicle, GitHub #49."""
from __future__ import annotations
from ...analysis import elf
from .. import applicability as ap
from ..base import ApkContext, Patch, register
from . import artifact
@@ -48,12 +49,13 @@ class ZinkShaderFix(Patch):
"the adapter setting zink_shader_fix into the SPIR-V modules whose size and SHA-256 match (every "
"other module is untouched), for example stores that set loop counters and accumulators a shader "
"reads before writing, which hang the GPU (Vader Immortal's lightspeed jump; found by Klownicle, "
"GitHub #49). Does nothing without zink_shader_fix (or zink_shader_dump) in the recipe. The layer "
"GitHub #49). Does nothing without zink_shader_fix (or zink_shader_dump) in the recipe, and either "
"setting brings it in by itself. The layer "
"adds itself to Android's Vulkan debug layer list for the game's process only.")
order = 73
def applies(self, a):
return "GLES" in a.graphics and "arm64-v8a" in a.abis
return ap.may_be_gles(a) and "arm64-v8a" in a.abis
def apply(self, ctx: ApkContext) -> bool:
ws = ctx.ws
+10 -3
View File
@@ -16,6 +16,9 @@ BASE_SETTINGS = {"scale": 1.0, "foveation_fix": 1, "controller_fix": 1}
# key, kind, default, title, description
SETTINGS = [
("fixed_resolution", "int", 0, "Keep full resolution (no dynamic resolution)",
"Keeps OpenXR layer recommendations at the full swapchain size. Prevents dynamic resolution from moving "
"Valve foveation off-centre or leaving a coarse white edge line. May increase GPU load."),
("scale", "float", 1.0, "Resolution scale",
"Multiplies the recommended eye-buffer width and height (0.5–2.0). 1.5 ≈ 2.25× pixels."),
("foveation_fix", "int", 1, "Hide Quest foveation", "Hides Quest foveation extensions the Frame runtime lacks."),
@@ -325,6 +328,9 @@ FOVEATION_UI = {
}
UI: dict[str, dict] = {
"fixed_resolution": dict(group="picture", level="advanced", label="Keep full resolution (no dynamic resolution)",
help="Prevents dynamic resolution from moving Valve foveation off-centre or leaving a white edge line. "
"May increase GPU load. Used at the next launch.", control=("switch",)),
"scale": dict(group="picture", level="common", label="Render resolution (supersampling)",
help="Changes the game's render resolution. Higher is sharper but slower; 1.0× is the game's own "
"resolution.",
@@ -538,7 +544,7 @@ UI: dict[str, dict] = {
# settings that need the xrshim (frame/adapter.py: OVRPlugin's own arm64 OpenXR lookups routed to the adapter), so
# they don't work for 32-bit games on the bridge. surface_native: Batman's native video renderer hooks Vulkan
# functions OVRPort's dispatcher doesn't know
XRSHIM_SETTINGS = ("hide_fb_foveation", "controller_models", "haptic_fix", "surface_native")
XRSHIM_SETTINGS = ("fixed_resolution", "hide_fb_foveation", "controller_models", "haptic_fix", "surface_native")
# shown with an "experimental" pill (Game settings, game page, CLI): not proven in the headset on many games yet
EXPERIMENTAL_SETTINGS = ("quest_buttons",)
@@ -625,6 +631,7 @@ class AdapterSetting(Patch):
or "com.oculus.feature.PASSTHROUGH" in (a.extra.get("features") or {})),
"alpha_blend_emul": lambda a: (a.extra.get("mr_only")
or "com.oculus.feature.PASSTHROUGH" in (a.extra.get("features") or {})),
"fixed_resolution": lambda a: "libOVRPlugin.so" in a.libs or a.xr == "OpenXR",
"hide_fb_foveation": ap.is_vulkan,
"flip_emul": ap.is_vulkan,
"flip_quads": ap.is_vulkan,
@@ -647,8 +654,8 @@ class AdapterSetting(Patch):
"vk_pipeline_fallback": lambda a: a.engine in ("Unreal", "Other") and "arm64-v8a" in a.abis,
# wherever the Vulkan shim can be (vk_sanitize); Unreal's Vulkan builds are rarely detected as such
"vk_shader_dump": lambda a: a.engine in ("Unreal", "Other") and "arm64-v8a" in a.abis,
"zink_shader_fix": ap.is_gles,
"zink_shader_dump": ap.is_gles,
"zink_shader_fix": ap.may_be_gles,
"zink_shader_dump": ap.may_be_gles,
"haptic_fix": lambda a: "libOVRPlugin.so" in a.libs,
"surface_native": lambda a: "arm64-v8a" in a.abis, # the xrshim it needs is arm64 only (also above)
"pose_time_fix": lambda a: "libOVRPlugin.so" in a.libs,
+34
View File
@@ -0,0 +1,34 @@
#include <assert.h>
#include <stdint.h>
#include <openxr/openxr.h>
static int swapchain_size(XrSwapchain handle, uint32_t *w, uint32_t *h) {
if (!handle) return 0;
*w = handle == (XrSwapchain)(uintptr_t)2 ? 1024 : 2768; *h = *w;
return 1;
}
#include "fixed_resolution.h"
int main(void) {
XrExtent2Di full = {0};
XrCompositionLayerProjectionView views[2] = {{XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW},
{XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW}};
XrCompositionLayerProjection p = {XR_TYPE_COMPOSITION_LAYER_PROJECTION};
p.views = views; p.viewCount = 2;
for (int i=0; i<2; ++i) {
views[i].subImage.swapchain = (XrSwapchain)(uintptr_t)1;
views[i].subImage.imageRect = (XrRect2Di){{0,64},{2704,2704}};
}
assert(fixed_layer_size((void *)&p, &full));
assert(full.width == 2768 && full.height == 2768);
assert(views[0].subImage.imageRect.extent.width == 2704); // never stretch a submitted subrect
views[1].subImage.swapchain = (XrSwapchain)(uintptr_t)2;
assert(!fixed_layer_size((void *)&p, &full));
views[1].subImage.swapchain = XR_NULL_HANDLE;
assert(!fixed_layer_size((void *)&p, &full));
p.viewCount=0; assert(!fixed_layer_size((void *)&p, &full));
assert(!fixed_layer_size(NULL, &full));
XrCompositionLayerQuad q = {XR_TYPE_COMPOSITION_LAYER_QUAD};
q.subImage.swapchain=(XrSwapchain)(uintptr_t)2;
assert(fixed_layer_size((void *)&q, &full) && full.width == 1024);
q.type=XR_TYPE_UNKNOWN; assert(!fixed_layer_size((void *)&q, &full));
return 0;
}
+45
View File
@@ -0,0 +1,45 @@
#include <assert.h>
#include <stdint.h>
#include <stddef.h>
#include <stdarg.h>
#include <openxr/openxr.h>
static int missing, valid_size=1, calls;
static XrInstance active_instance;
static XrResult XRAPI_CALL runtime(XrSession session, const XrRecommendedLayerResolutionGetInfoMETA *info,
XrRecommendedLayerResolutionMETA *out) {
(void)session; (void)info; ++calls;
out->recommendedImageDimensions=(XrExtent2Di){2704,2704};
out->isValid=XR_FALSE;
return XR_SUCCESS;
}
static PFN_xrVoidFunction lookup(XrInstance instance, const char *name) {
(void)instance; (void)name; return missing ? NULL : (PFN_xrVoidFunction)runtime;
}
static int swapchain_size(XrSwapchain handle, uint32_t *w, uint32_t *h) {
(void)handle; *w=2768; *h=2768; return valid_size;
}
static void log_stub(const char *fmt, ...) { (void)fmt; }
#define LOG(...) log_stub(__VA_ARGS__)
#include "fixed_resolution.h"
/* WRAPPER */
int main(void) {
XrCompositionLayerQuad layer={XR_TYPE_COMPOSITION_LAYER_QUAD};
XrRecommendedLayerResolutionGetInfoMETA info={XR_TYPE_RECOMMENDED_LAYER_RESOLUTION_GET_INFO_META};
info.layer=(void *)&layer;
XrRecommendedLayerResolutionMETA out={XR_TYPE_RECOMMENDED_LAYER_RESOLUTION_META};
int tail=42; out.next=&tail;
for (missing=0; missing<2; ++missing) {
assert(fixed_recommended_resolution(XR_NULL_HANDLE,&info,&out)==XR_SUCCESS);
assert(out.isValid==XR_TRUE && out.recommendedImageDimensions.width==2768);
assert(out.recommendedImageDimensions.height==2768 && out.next==&tail && tail==42);
}
assert(calls==1);
valid_size=0; missing=0;
assert(fixed_recommended_resolution(XR_NULL_HANDLE,&info,&out)==XR_SUCCESS);
assert(out.recommendedImageDimensions.width==2704 && !out.isValid);
missing=1;
assert(fixed_recommended_resolution(XR_NULL_HANDLE,&info,&out)==XR_ERROR_FUNCTION_UNSUPPORTED);
assert(fixed_recommended_resolution(XR_NULL_HANDLE,NULL,&out)==XR_ERROR_VALIDATION_FAILURE);
assert(fixed_recommended_resolution(XR_NULL_HANDLE,&info,NULL)==XR_ERROR_VALIDATION_FAILURE);
return 0;
}
+15
View File
@@ -5,8 +5,15 @@
#include <string.h>
#include <openxr/openxr.h>
static int enabled, requested;
static XrResult XRAPI_CALL recommended(XrSession session, const XrRecommendedLayerResolutionGetInfoMETA *info,
XrRecommendedLayerResolutionMETA *out) {
(void)session; (void)info;
out->recommendedImageDimensions = (XrExtent2Di){2768,2768}; out->isValid = XR_TRUE;
return XR_SUCCESS;
}
static int query(void) { return enabled; }
static PFN_xrVoidFunction extension(const char *name) {
if (enabled && !strcmp(name,"xrGetRecommendedLayerResolutionMETA")) return (PFN_xrVoidFunction)recommended;
return !strcmp(name,"framebridge_hide_fb_foveation") ? (PFN_xrVoidFunction)query : NULL;
}
static XrResult XRAPI_CALL enumerate(const char *layer,uint32_t capacity,uint32_t *count,XrExtensionProperties *out) {
@@ -57,5 +64,13 @@ int main(void) {
assert(((PFN_xrCreateInstance)fn)(&ci,NULL)==XR_SUCCESS);
assert(requested==(enabled ? 1 : 2) && ci.enabledExtensionCount==2);
}
enabled=0;
assert(xrGetInstanceProcAddr((XrInstance)(uintptr_t)1,"xrGetRecommendedLayerResolutionMETA",&fn)
== XR_ERROR_FUNCTION_UNSUPPORTED);
enabled=1;
assert(xrGetInstanceProcAddr((XrInstance)(uintptr_t)1,"xrGetRecommendedLayerResolutionMETA",&fn)==XR_SUCCESS);
XrRecommendedLayerResolutionMETA out={XR_TYPE_RECOMMENDED_LAYER_RESOLUTION_META};
assert(((PFN_xrGetRecommendedLayerResolutionMETA)fn)(XR_NULL_HANDLE,NULL,&out)==XR_SUCCESS);
assert(out.isValid && out.recommendedImageDimensions.width==2768);
return 0;
}
+85
View File
@@ -0,0 +1,85 @@
"""Full swapchain recommendations and opt-in OVRPlugin routing."""
import os
import shutil
import subprocess
from pathlib import Path
import pytest
from test_patches import _analysis
from frameport.patches import base
from frameport.patches.frame.adapter import needs_xrshim
from frameport.patches.settings import adapter_settings, UI
ROOT = Path(__file__).resolve().parents[1]
def test_setting_is_opt_in_and_routes_ovrplugin():
base.load_all()
patch = base.get("adapter.fixed_resolution")
assert patch.default == 0
assert patch.detect(_analysis()) is None
assert not adapter_settings({}).get("fixed_resolution")
assert needs_xrshim({patch.id: {"value": 1}})
assert not needs_xrshim({patch.id: {"value": 0}, "adapter.haptic_fix": {"value": 0}})
assert UI[patch.key]["group"] == "picture"
assert patch.applies(_analysis(xr="OpenXR", libs=[]))
assert patch.applies(_analysis(xr="VrApi", libs=["libOVRPlugin.so"]))
assert not patch.applies(_analysis(xr="VrApi", libs=[]))
assert not patch.applies(_analysis(abis=["armeabi-v7a"], xr="OpenXR"))
@pytest.mark.native
def test_layer_dimensions(tmp_path):
inc = next(iter((ROOT / "native/.cache").glob("openxr-*")), None)
cc = shutil.which("cc")
if not os.environ.get("FRAMEPORT_NATIVE_TESTS") or not inc or not cc:
pytest.skip("needs FRAMEPORT_NATIVE_TESTS=1, OpenXR headers and cc")
exe = tmp_path / "fixed-resolution"
subprocess.run([cc, "-std=c11", "-Wall", "-Wextra", "-Werror",
"-Wno-missing-field-initializers", "-I", str(inc),
"-I", str(ROOT / "native/adapter"),
str(ROOT / "tests/fixtures/src/fixed_resolution_test.c"), "-o", str(exe)], check=True)
subprocess.run([str(exe)], check=True)
def test_triage_only_offers_fix_for_offset_rects():
from frameport.validate.triage import triage
log = "projection view 0 rect change: swapchain=0x123 rect=0,32 2736x2736 array=0"
found = [f for f in triage(log).findings if f.id == "dynamic-resolution-foveation"]
assert found and found[0].suggest == ["adapter.fixed_resolution"] and found[0].question
assert not any(f.id == "dynamic-resolution-foveation" for f in
triage(log.replace("rect=0,32", "rect=0,0")).findings)
assert not any(f.id == "dynamic-resolution-foveation" for f in
triage(log + "\nfixed_resolution: recommended 2704x2704 -> 2768x2768").findings)
def test_catalog_requires_app_with_the_fix(monkeypatch):
import frameport
import yaml
from frameport.recommend.catalog import unusable_reason
entry = yaml.safe_load((ROOT / "catalog/games/com.camouflaj.manta.yaml").read_text())
assert entry["adapter"]["fixed_resolution"] == 1
monkeypatch.setattr(frameport, "__version__", "1.2.1")
assert unusable_reason(entry) == "needs FramePort 1.2.2"
monkeypatch.setattr(frameport, "__version__", "1.2.2")
assert unusable_reason(entry) is None
@pytest.mark.native
def test_recommendation_wrapper(tmp_path):
inc = next(iter((ROOT / "native/.cache").glob("openxr-*")), None)
cc = shutil.which("cc")
if not os.environ.get("FRAMEPORT_NATIVE_TESTS") or not inc or not cc:
pytest.skip("needs FRAMEPORT_NATIVE_TESTS=1, OpenXR headers and cc")
# Compile the actual wrapper with a stand-in runtime and swapchain table.
source = (ROOT / "native/adapter/frame_adapter.c").read_text()
wrapper = source[source.index("static XRAPI_ATTR XrResult XRAPI_CALL fixed_recommended_resolution"):
source.index("// Clamp `r` to a w x h image;")]
harness = (ROOT / "tests/fixtures/src/fixed_resolution_wrapper_test.c").read_text()
unit = tmp_path / "wrapper.c"
unit.write_text(harness.replace("/* WRAPPER */", wrapper))
exe = tmp_path / "wrapper"
subprocess.run([cc, "-std=c11", "-Wall", "-Wextra", "-Werror",
"-Wno-missing-field-initializers", "-I", str(inc),
"-I", str(ROOT / "native/adapter"), str(unit), "-o", str(exe)], check=True)
subprocess.run([str(exe)], check=True)
+33
View File
@@ -0,0 +1,33 @@
"""GitHub #382: Unity games whose boot.config merely mentions Vulkan often render with OpenGL ES (SynthRiders), so the
OpenGL ES shader dump/fix stay offered for them, and either setting brings in the shader-fix layer by itself."""
from frameport.build import apk_patches
from frameport.core.models import Analysis
from frameport.patches import base
def analysis(graphics):
return Analysis(package="com.kluge.SynthRiders", version="3.6.6a2", label="Synth Riders", abis=["arm64-v8a"],
engine="Unity", xr="OpenXR", graphics=graphics, direct_vrapi=False,
libs=["libunity.so", "libOVRPlugin.so"], launcher_activity=None, has_info_category=False,
meta_permissions=[], uses_glad_gl=False, unity_msaa_levels=0, oculus_os_classes=False,
is_overport_output=False, debuggable=False, extra={})
def test_shader_dump_offered_for_unity_boot_config_vulkan():
a = analysis("Vulkan (Unity boot.config)")
for pid in ("adapter.zink_shader_dump", "adapter.zink_shader_fix", "frame.zink_shader_fix"):
assert base.get(pid).applies(a), pid
def test_shader_dump_hidden_for_declared_vulkan():
a = analysis("Vulkan (declared in manifest)")
for pid in ("adapter.zink_shader_dump", "adapter.zink_shader_fix", "frame.zink_shader_fix"):
assert not base.get(pid).applies(a), pid
def test_dump_setting_alone_adds_the_layer():
ids = [p.id for p in apk_patches({"frame.adapter": {}, "adapter.zink_shader_dump": {"value": 1}})]
assert ids.count("frame.zink_shader_fix") == 1
ids = [p.id for p in apk_patches({"frame.zink_shader_fix": {}, "adapter.zink_shader_dump": {"value": 1}})]
assert ids.count("frame.zink_shader_fix") == 1
assert "frame.zink_shader_fix" not in [p.id for p in apk_patches({"adapter.zink_shader_dump": {"value": 0}})]