mirror of
https://github.com/spoopyghosty0/frameport.git
synced 2026-10-19 05:01:27 +02:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38eb90a0d6 | ||
|
|
54effe9ab0 | ||
|
|
d78fcbd20a | ||
|
|
0322b8b4d6 |
No files matched your search
@@ -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,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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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
@@ -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 -> false and isKeyboardUsingEvents (TMP) / InPlaceEditing (uGUI) -> 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".
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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}})]
|
||||
Reference in new issue
Block a user