mirror of
https://github.com/spoopyghosty0/frameport.git
synced 2026-10-18 03:01:40 +02:00
Compare commits
6
Commits
showcase/update
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01ad5e6b97 | ||
|
|
33d306a11e | ||
|
|
8e463a55de | ||
|
|
faca9bf396 | ||
|
|
e0a9b5f7cb | ||
|
|
3e906d55cf |
No files matched your search
@@ -46,7 +46,7 @@ try:
|
||||
except ImportError: # Windows: pc_revive loads this file for its VDF code only (GitHub #131)
|
||||
fcntl = None
|
||||
|
||||
AGENT_VERSION = 99
|
||||
AGENT_VERSION = 100
|
||||
HOME = os.path.expanduser("~")
|
||||
STEAM = os.path.join(HOME, ".local/share/Steam")
|
||||
ANCHORS = os.path.join(HOME, "Applications/quest-frame")
|
||||
@@ -5716,7 +5716,7 @@ def cmd_keep_awake(args):
|
||||
|
||||
|
||||
FOVEATION_ENV_KEYS = {"FDM_DEBUG", "ENABLE_VULKAN_FDM_INJECTION_LAYER",
|
||||
"LEPTON_ENV_DISABLE_VULKAN_FDM_INJECTION_LAYER", "VK_INSTANCE_LAYERS", "FOVE_INNER", "FOVE_OUTER",
|
||||
"LEPTON_ENV_DISABLE_VULKAN_FDM_INJECTION_LAYER", "VK_INSTANCE_LAYERS", "FOVE_INNER", "FOVE_OUTER", "FOVE_LEVEL",
|
||||
"LEPTON_ENV_FRAMEPORT_FOVEATION_TEST", "LEPTON_ENV_FRAMEPORT_CLIMB_STEREO_OFFSETS",
|
||||
"LEPTON_ENV_FRAMEPORT_VRAPI_FDM_ISOLATE_MONO", "LEPTON_ENV_FRAMEPORT_VRAPI_KEEP_FOCUSED_IMAGE"}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package: ScaredBySquares.Game
|
||||
title: Scared By Squares
|
||||
status: unknown
|
||||
status: works
|
||||
notes: 'Showed black and dropped back to the home environment while the game kept running (sound only, GitHub 323).
|
||||
Fixed in FramePort 1.2.1 and checked on the Frame without the headset (the game runs at 72 fps and SteamVR keeps it
|
||||
as the running game); not yet confirmed in the headset. Rebuild and reinstall the game after updating FramePort.'
|
||||
Fixed in FramePort 1.2.1 (confirmed in the headset by the reporter). Rebuild and reinstall the game after updating
|
||||
FramePort.'
|
||||
details: 'The game (Unreal 4.27, OpenXR) creates two controller actions named "turn" (an action mapping and an axis
|
||||
mapping called Turn). The Frame''s runtime accepts the duplicate, then fails to load the input manifest it builds
|
||||
from the actions ("SetXrInputConfig failed. Failed to load manifest") and treats the game as closed. FrameBridge''s
|
||||
@@ -14,6 +14,9 @@ xr: OpenXR
|
||||
frame:
|
||||
- frame.hw_video_decode
|
||||
- frame.unreal_skip_obb_check
|
||||
updated: '2026-10-16'
|
||||
verified:
|
||||
date: '2026-10-17'
|
||||
issue: 323
|
||||
updated: '2026-10-17'
|
||||
min_app: 1.2.1
|
||||
source_hint: Scared By Squares
|
||||
@@ -0,0 +1,25 @@
|
||||
package: com.CortopiaStudios.Spectre
|
||||
title: Spectre
|
||||
status: works
|
||||
notes: Works since 1.2.1 (the loading screen waited for a Meta analytics request).
|
||||
tested_version: 1.0.1.1354
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.unity_text_input
|
||||
- frame.hw_video_decode
|
||||
- frame.cortopia_org_id
|
||||
device:
|
||||
- device.text_input_window
|
||||
adapter:
|
||||
haptic_fix: 1
|
||||
scale: 2.0
|
||||
verified:
|
||||
date: '2026-10-17'
|
||||
known_good_sha256: a37a5400ef58906b575f6da1649b62ac7ce024eab831fae105457c1d5d12c2de
|
||||
app: 1.2.1
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261007.6125817'
|
||||
agent: 96
|
||||
issue: 390
|
||||
source_hint: Wonder
|
||||
@@ -0,0 +1,24 @@
|
||||
package: com.cosmorama.tabletroopers
|
||||
title: Table Troopers
|
||||
status: works
|
||||
tested_version: 2026.40.4
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.gms_platform_version
|
||||
- frame.photon_voice_audio_route
|
||||
- frame.unity_text_input
|
||||
device:
|
||||
- device.text_input_window
|
||||
adapter:
|
||||
haptic_fix: 1
|
||||
scale: 2.0
|
||||
verified:
|
||||
date: '2026-10-17'
|
||||
known_good_sha256: b0a4b0e29855277712b580bd5507721fff137bde77b1911b34cca89c627feef1
|
||||
app: 1.2.1
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261007.6125817'
|
||||
agent: 96
|
||||
issue: 391
|
||||
source_hint: Troopers
|
||||
@@ -0,0 +1,12 @@
|
||||
package: com.rrrgames.ThiefSimVRQuest
|
||||
title: Thief Simulator
|
||||
status: unsupported
|
||||
notes: Quits at start with the game's own "installed from unknown source" message. Lepton installs the game itself at
|
||||
every start (there is no Meta store app on the Frame), so the game never sees the Meta store as its source.
|
||||
tested_version: 1.1.7.3
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
verified:
|
||||
date: '2026-10-17'
|
||||
issue: 368
|
||||
updated: '2026-10-17'
|
||||
+51
-8
@@ -398,14 +398,57 @@ over Quest games.
|
||||
|
||||
Under a Quest game's **Game settings → Picture**, choose **Foveated rendering → Eye-tracked**, then **Save**.
|
||||
Changes apply at the next game start.
|
||||
The full-resolution area selector defaults to **Default (30% radius)**: no radius override, preserving the
|
||||
runtime profile used in the headset experiments. Larger radii retain more detail and save less shading work.
|
||||
Custom radii set `FOVE_INNER`; `FOVE_OUTER` is twice that value, matching the runtime's profile proportions.
|
||||
The inspected runtime's built-in inner/outer pairs are 0.30/0.60, 0.25/0.50 and 0.20/0.40.
|
||||
The shared-texture injection path selects profile 1: 0.30 inner radius and 0.60 outer radius.
|
||||
The area selector now uses Valve's supported `FOVE_LEVEL` presets (GitHub #394; binary analysis
|
||||
2026-10-17). Default leaves the runtime choice alone; on the shared injection path this is `lo`.
|
||||
The old `FOVE_INNER`/`FOVE_OUTER` radius exports had no effect: Lepton does not pass them through,
|
||||
and the layer does not read them. The previously documented 0.30/0.60, 0.25/0.50, 0.20/0.40
|
||||
radius pairs do **not** describe the generated shared-injection density map.
|
||||
|
||||
The inspected arm64 `libVkLayer_VALVE_fdm_injection.so` has these semantics (virtual addresses):
|
||||
- `init_fdm_fove_level` at `0x1cdf4` calls the cached option reader, then
|
||||
`debug_parse_flags_option` (`0x21d74`) with table `0x27e70`: named tokens `lo` → 1,
|
||||
`med` → 2, `hi` → 3. It does not call atoi/strtol. Unset, empty, unknown and numeric
|
||||
strings parse to zero and retain the current profile (initialized to 1 at `0x1ce74`).
|
||||
Tokens are case-sensitive and separated by non-alphanumeric/non-underscore characters;
|
||||
matching values are ORed, so combinations can alias another profile. `all` also yields 3;
|
||||
`help` prints the table and returns zero. Use a single preset token.
|
||||
- Profile selection at `0x1918c–0x19364` uses normalized **rectangles**, not circular radii.
|
||||
`lo` has a full-density center rectangle `(x=.364, y=.367, width=.295, height=.286)`
|
||||
(constant at `0xe440`), then density byte `0x76` outside. `med` uses rectangles from
|
||||
`0xe988`: sizes `.250×.245`, `.295×.286`, `.386×.367`, with density bytes
|
||||
`0xff`, `0xa8`, `0x76` and `0x50` outside. `hi` uses `0xe9b8`: sizes
|
||||
`.246×.211`, `.281×.246`, `.351×.316`, `.386×.351`, with bytes
|
||||
`0xff`, `0xa8`, `0x76`, `0x50`, then `0x38` outside. Each byte is written to both
|
||||
R8G8 density channels. All three retain a full-density center (`0xff`); medium/high make it smaller
|
||||
and reduce peripheral density more. Coordinates are sampled on the density-map grid (`0x19378` onward).
|
||||
- `FDM_DEBUG` flags `lo`/`med`/`hi` also select presets (high takes precedence), but an
|
||||
explicit recognized `FOVE_LEVEL` overrides them (`0x1ce14`). `enable` gates injection;
|
||||
`disable_offsets` fixes the gaze position (`0x1b6c8`), and `yflip` flips the vertical
|
||||
offset (`0x1b9b8`). These three flags do not resize the profile.
|
||||
- `FDM_SWAPCHAIN_SIZE` is parsed by `atoi` (`0x1c518`), clamped to nonnegative, and
|
||||
seeds a known swapchain size (`0x1ccf8`). It is a size/matching hint, not a radius.
|
||||
The synthetic map is square with side `ceil(size/32)` (`0x19048–0x19088`); image
|
||||
association also matches this tile size (`0x16c78`). Changing render resolution changes
|
||||
the pixel extent, tile quantization and potentially the matched render target/map. This can
|
||||
change the visible diagnostic area without making the ignored radius exports effective;
|
||||
a matched device capture is needed to establish which path a particular game used.
|
||||
|
||||
Per-game library preferences now store `level`. Legacy radius choices are translated on read
|
||||
(and written as `level` on Save): nearest old nominal .30 → lo, .25 → med, .20 → hi,
|
||||
with larger values clamped by nearest choice; zero or unknown/invalid values → Default.
|
||||
This preserves approximate intent, not a claim that these presets implement those radii.
|
||||
Explicit `level` wins. Obsolete radius exports are removed from managed launchers.
|
||||
|
||||
For device verification, hold game scene, render resolution and gaze constant; compare explicit
|
||||
`lo`, `med`, `hi` across fresh game starts (Default should match lo absent another runtime override).
|
||||
First use Fixed for a stable comparison, then Eye-tracked to check gaze following. Enable
|
||||
**Darken coarse fragments (test)** and adapter `snapshot=N` to capture submitted eye images headlessly
|
||||
every N seconds (GLES only, left eye at quarter size); compare the undarkened center and peripheral bands. Existing adapter
|
||||
builds need diagnostic support installed. Turn the test and snapshots off before benchmarking.
|
||||
Agent `FOVEATION_ENV_KEYS` must include `"FOVE_LEVEL"` for launcher updates via `set_settings`.
|
||||
|
||||
`device.foveation` enables Valve's shared Vulkan injection and suppresses competing native foveation through
|
||||
`foveation_fix=1`. Game settings stores mode, radius and test selection as a per-game library preference, outside shared recipes. This remains
|
||||
`foveation_fix=1`. Game settings stores mode, profile and test selection as a per-game library preference, outside shared recipes. This remains
|
||||
experimental: complete images were confirmed in RE4, Batman, Deadpool, The Climb 2 and Asgard's Wrath 2,
|
||||
not all Quest games or renderers. RE4 and Batman checks include title/calibration scenes; these are not
|
||||
equivalent to extended gameplay coverage. Asgard gameplay was visually confirmed, without an on/off benchmark.
|
||||
@@ -432,7 +475,7 @@ in Climb the same library also carries the compatibility correction.
|
||||
|
||||
High-resolution Deadpool samples crossed from about 45 to 90 FPS with injection enabled, but temperatures and
|
||||
power rose as it rendered twice as many frames. Foveation does not guarantee lower temperature at uncapped FPS.
|
||||
The new configurable radii have configuration/build coverage; each radius has not been separately play-tested.
|
||||
Preset export/migration has configuration coverage; these preset comparisons have not been device-tested here.
|
||||
|
||||
## Monitoring sources (probed 2026-10-07, SteamOS 0.4.3, kernel 6.18; used by the Monitor tab, agent `_monitor`)
|
||||
|
||||
@@ -459,7 +502,7 @@ game processes without one), temperatures every 2 s, the CPU-cluster power rails
|
||||
|
||||
### Foveation user preferences (discussion #365)
|
||||
|
||||
Game settings → Picture provides Default / Eye-tracked / Fixed / Off, radius, and an advanced diagnostic switch.
|
||||
Game settings → Picture provides Default / Eye-tracked / Fixed / Off, a runtime profile, and an advanced diagnostic switch.
|
||||
`set_settings` (agent 98) atomically rewrites just the managed launcher exports and records them in deployment.json;
|
||||
no APK rebuild, reinstall or live device intervention is needed. Changes apply at the next start. Default restores
|
||||
recipe/catalog launcher defaults; explicit preferences win. One-time library migration extracts old recipe choices
|
||||
|
||||
@@ -117,6 +117,7 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| Riven | Quest | ✅ Works | |
|
||||
| Robo Recall | Quest | ✅ Works | |
|
||||
| RUINSMAGUS | Quest | ✅ Works | |
|
||||
| Scared By Squares | Quest | ✅ Works | |
|
||||
| Sclerosis | Quest | ✅ Works | |
|
||||
| Shadow Point | Quest | ✅ Works | |
|
||||
| Shoot The Zombirds VR | Quest | ✅ Works | |
|
||||
@@ -126,6 +127,7 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| Sniper Elite VR | Quest | ✅ Works | |
|
||||
| Sniper Elite VR: Winter Warrior | Quest | ✅ Works | |
|
||||
| Space Pirate Trainer Quest | Quest | ✅ Works | |
|
||||
| Spectre | Quest | ✅ Works | |
|
||||
| Sports Scramble (Santa Cruz) | Quest | ✅ Works | |
|
||||
| Star Wars Pinball VR | Quest | ✅ Works | |
|
||||
| Star Wars: Tales from the Galaxy's Edge | Quest | ✅ Works | |
|
||||
@@ -134,6 +136,7 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| SUPERHOT VR | PC VR | ✅ Works | |
|
||||
| SUPERHOT VR | Quest | ✅ Works | |
|
||||
| SynthRiders | Quest | ✅ Works | |
|
||||
| Table Troopers | Quest | ✅ Works | |
|
||||
| Taskmaster | Quest | ✅ Works | |
|
||||
| Tea For God | Quest | ✅ Works | |
|
||||
| TetrisEffect | Quest | ✅ Works | |
|
||||
@@ -204,3 +207,4 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| GOLF+ | Quest | ❌ Doesn't run | Black screen. |
|
||||
| HITMAN 3 VR: Reloaded | Quest | ❌ Doesn't run | Vulkan driver crash (freedreno), even without Valve layers. |
|
||||
| Roblox | Quest | ❌ Doesn't run | Crashes on its first VR frame on the Frame. |
|
||||
| Thief Simulator | Quest | ❌ Doesn't run | Quits at start with the game's own "installed from unknown source" message. |
|
||||
+18
-15
@@ -52,13 +52,13 @@ Link to an entry with its id, dots replaced by dashes: `PATCHES.md#frame-unity_t
|
||||
| [Skip Meta avatars](#frame-avatar_stub) | `frame.avatar_stub` | Steam Frame patches | 1 |
|
||||
| [bHaptics: skip the Bluetooth permission request](#frame-bhaptics_permission) | `frame.bhaptics_permission` | Steam Frame patches | 6 |
|
||||
| [BlazeRush: controllers without Meta avatars, own shader cache](#frame-blazerush) | `frame.blazerush` | Steam Frame patches | 1 |
|
||||
| [Cortopia games: don't wait for Meta's org-scoped id](#frame-cortopia_org_id) | `frame.cortopia_org_id` | Steam Frame patches | |
|
||||
| [Cortopia games: don't wait for Meta's org-scoped id](#frame-cortopia_org_id) | `frame.cortopia_org_id` | Steam Frame patches | 1 |
|
||||
| [Epic Online Services: stop the keychain retry loop](#frame-eos_keychain_loop) | `frame.eos_keychain_loop` | Steam Frame patches | |
|
||||
| [OpenGL ES: multiview shaders on flat panels (HUD, menus)](#frame-gl_multiview_fbo) | `frame.gl_multiview_fbo` | Steam Frame patches | 1 |
|
||||
| [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 | |
|
||||
| [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 | 31 |
|
||||
| [Hardware video decoding (H.264, HEVC, VP9)](#frame-hw_video_decode) | `frame.hw_video_decode` | Steam Frame patches | 32 |
|
||||
| [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 |
|
||||
@@ -73,7 +73,7 @@ Link to an entry with its id, dots replaced by dashes: `PATCHES.md#frame-unity_t
|
||||
| [Platform compat (ovrMessageType_ToString)](#frame-ovrplatformcompat) | `frame.ovrplatformcompat` | Steam Frame patches | default |
|
||||
| [Stub missing Meta platform functions](#frame-ovrstubs) | `frame.ovrstubs` | Steam Frame patches | default |
|
||||
| [Unpaired pointer-authentication checks](#frame-pac_hints) | `frame.pac_hints` | Steam Frame patches | 1 |
|
||||
| [Photon Voice: skip Android 12 audio routing](#frame-photon_voice_audio_route) | `frame.photon_voice_audio_route` | Steam Frame patches | |
|
||||
| [Photon Voice: skip Android 12 audio routing](#frame-photon_voice_audio_route) | `frame.photon_voice_audio_route` | Steam Frame patches | 1 |
|
||||
| [SDL apps: start without a clipboard service](#frame-sdl_clipboard) | `frame.sdl_clipboard` | Steam Frame patches | 5 |
|
||||
| [Stress Level Zero: let Unity start Vulkan itself](#frame-slz_vulkan_hooks) | `frame.slz_vulkan_hooks` | Steam Frame patches | 1 |
|
||||
| [Start straight in VR (skip the 2D launcher)](#frame-start_activity) | `frame.start_activity` | Steam Frame patches | |
|
||||
@@ -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 | 31 |
|
||||
| [Make Unity text fields work](#frame-unity_text_input) | `frame.unity_text_input` | Steam Frame patches | 111 |
|
||||
| [Make Unity text fields work](#frame-unity_text_input) | `frame.unity_text_input` | Steam Frame patches | 113 |
|
||||
| [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 |
|
||||
@@ -136,7 +136,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 | 96 |
|
||||
| [Fix freezes when controllers vibrate](#adapter-haptic_fix) | `adapter.haptic_fix` | Game settings | 98 |
|
||||
| [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 |
|
||||
@@ -156,7 +156,7 @@ Link to an entry with its id, dots replaced by dashes: `PATCHES.md#frame-unity_t
|
||||
| [Refresh rate](#adapter-refresh_rate) | `adapter.refresh_rate` | Game settings | 3 |
|
||||
| [Wait for the game's GPU](#adapter-release_wait) | `adapter.release_wait` | Game settings | |
|
||||
| [Re-create play space](#adapter-respace_kick) | `adapter.respace_kick` | Game settings | |
|
||||
| [Render resolution (supersampling)](#adapter-scale) | `adapter.scale` | Game settings | 9 |
|
||||
| [Render resolution (supersampling)](#adapter-scale) | `adapter.scale` | Game settings | 11 |
|
||||
| [Room depth](#adapter-scene_depth) | `adapter.scene_depth` | Game settings | |
|
||||
| [Pretend room layout](#adapter-scene_emul) | `adapter.scene_emul` | Game settings | 12 |
|
||||
| [Room height](#adapter-scene_height) | `adapter.scene_height` | Game settings | |
|
||||
@@ -188,7 +188,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 | 114 |
|
||||
| [Show the app's Android window (for typing)](#device-text_input_window) | `device.text_input_window` | Files and settings on the Frame | 116 |
|
||||
| [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) | |
|
||||
@@ -607,6 +607,7 @@ Gets Cortopia games (for example Escaping Wonderland) past their loading screen.
|
||||
- `frame.cortopia_org_id`
|
||||
- **Changes:** Changes the APK (a rebuild and reinstall apply it).
|
||||
- **Suggested (on):** Cortopia's Meta platform start-up waits for an org-scoped user id that OVRPort's platform library never answers (stuck loading screen).
|
||||
- **Catalog games (1):** Spectre.
|
||||
- **Suggested by launch-test signatures:**
|
||||
- `cortopia-org-id-wait` (error): The game waits forever on its loading screen for an answer from Meta's platform (an org-scoped user id, used as its analytics id) that OVRPort's platform library doesn't give.
|
||||
- **Source:** [patches/frame/cortopia_org_id.py](../src/frameport/patches/frame/cortopia_org_id.py)
|
||||
@@ -660,6 +661,7 @@ Lets games with Google Play services code start on the Frame (they'd crash at st
|
||||
- `frame.gms_platform_version` · also for 2D apps
|
||||
- **Changes:** Changes the APK (a rebuild and reinstall apply it).
|
||||
- **Suggested (on):** Google Play services' Android 12 checks were built in as 'always true'; on Lepton (Android 11) the game would crash at start.
|
||||
- **Catalog games (1):** Table Troopers.
|
||||
- **Suggested by launch-test signatures:**
|
||||
- `gms-android12-call` (fatal): Google Play services' code in the game called an Android 12 method (the game was built for Android 12 and newer, so its version checks were built in as "always true") and Lepton runs Android 11; the game crashes on its GoogleApiHandler thread at start (for example Titan Isles).
|
||||
- **Source:** [patches/frame/gms_platform_version.py](../src/frameport/patches/frame/gms_platform_version.py)
|
||||
@@ -687,7 +689,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 (31):** 4XVR Video Player, AugmentedEmpire, Batman: Arkham Shadow, Battlemarked, BattleTalent, Breathedge VR, Dance Central, Exploding Kittens VR and 23 more.
|
||||
- **Catalog games (32):** 4XVR Video Player, AugmentedEmpire, Batman: Arkham Shadow, Battlemarked, BattleTalent, Breathedge VR, Dance Central, Exploding Kittens VR and 24 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.
|
||||
@@ -882,6 +884,7 @@ Stops games with Photon Voice chat from crashing on the Frame when voice chat st
|
||||
- `frame.photon_voice_audio_route` · also for 2D apps
|
||||
- **Changes:** Changes the APK (a rebuild and reinstall apply it).
|
||||
- **Suggested (on):** Photon Voice calls Android 12 audio methods, which Lepton (Android 11) lacks, and crashes when voice chat starts.
|
||||
- **Catalog games (1):** Table Troopers.
|
||||
- **Suggested by launch-test signatures:**
|
||||
- `photon-voice-api31` (fatal): The game's Photon Voice microphone plugin calls Android 12 audio-routing methods (AudioManager communication devices) without checking the Android version; Lepton runs Android 11, so the app crashes when voice chat starts (for example Table Troopers at the tutorial).
|
||||
- **Source:** [patches/frame/photon_voice_audio_route.py](../src/frameport/patches/frame/photon_voice_audio_route.py)
|
||||
@@ -1059,7 +1062,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 (111):** AgeOfJoy, AllInOneSports, Ancient_Dungeon, Arcsmith, Arkio, Assassin's Creed Nexus, Attack on Titan VR: Unbreakable, Audica and 103 more.
|
||||
- **Catalog games (113):** AgeOfJoy, AllInOneSports, Ancient_Dungeon, Arcsmith, Arkio, Assassin's Creed Nexus, Attack on Titan VR: Unbreakable, Audica and 105 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.
|
||||
@@ -1773,7 +1776,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 (96):** AgeOfJoy, Ancient_Dungeon, Attack on Titan VR: Unbreakable, Audica, AugmentedEmpire, Battlemarked, BattleSisters, BattleTalent and 88 more.
|
||||
- **Catalog games (98):** AgeOfJoy, Ancient_Dungeon, Attack on Titan VR: Unbreakable, Audica, AugmentedEmpire, Battlemarked, BattleSisters, BattleTalent and 90 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.
|
||||
@@ -2067,7 +2070,7 @@ Changes the game's render resolution. Higher is sharper but slower; 1.0× is the
|
||||
- **Options:**
|
||||
- Value: float, default `1.0` (always written).
|
||||
- Game settings → Picture → "Render resolution (supersampling)": slider 0.5–2 in steps of 0.1.
|
||||
- **Catalog games (9):** 4XVR Video Player, BlazeRush, CrazyTaxiVR, cubism, FGO VR, I Expect You To Die 2, The 7th Guest VR, Vampire: The Masquerade - Justice and 1 more.
|
||||
- **Catalog games (11):** 4XVR Video Player, BlazeRush, CrazyTaxiVR, cubism, FGO VR, I Expect You To Die 2, Spectre, Table Troopers and 3 more.
|
||||
- **Source:** [patches/settings.py](../src/frameport/patches/settings.py)
|
||||
|
||||
> Multiplies the recommended eye-buffer width and height (0.5–2.0). 1.5 ≈ 2.25× pixels.
|
||||
@@ -2457,12 +2460,12 @@ Render less detail outside your gaze; adjust the full-resolution area or darken
|
||||
- **Options:**
|
||||
- `support`: bool, default `False` — Internal APK compatibility support
|
||||
- `mode`: str, default empty — Catalog default: tracked, fixed or off (empty = Valve's default)
|
||||
- `radius`: float, default `0.0`, 0–0.45 — Full-resolution radius; 0 keeps the current runtime default
|
||||
- `level`: str, default empty — Runtime profile: lo, med, hi (empty = Default)
|
||||
- `test`: bool, default `False` — Darken lower-resolution regions (diagnostic)
|
||||
- **Catalog games (5):** Ocean Rift, Quantum Void, Tea For God, The 7th Guest VR, Vampire: The Masquerade - Justice.
|
||||
- **Source:** [patches/settings.py](../src/frameport/patches/settings.py)
|
||||
|
||||
> Reduces shading away from the full-resolution area, following each eye's gaze. Default area size keeps the runtime's current profile unchanged. Custom radius is a fraction of the shorter eye-image dimension; the transition ends at twice that radius. Larger areas retain more detail but save less GPU work. Fixed stops the area following your eyes. The optional test darkens actual coarse-shaded fragments. Runtime choices are in Game settings. Tested on RE4, Deadpool and Asgard's Wrath 2. The Climb 2 gets a version-guarded private bridge at 1.0 scale; Batman gets its hash-verified allocation fix (unsupported OVRPlugin versions are refused). Other games may need additional fixes; turn this off if artifacts appear.
|
||||
> Reduces shading away from the full-resolution area, following each eye's gaze. Default keeps the runtime profile unchanged. Low has the largest full-resolution center; medium and high use smaller centers and stronger peripheral detail reduction. Larger areas retain more detail but save less GPU work. Fixed stops the area following your eyes. The optional test darkens actual coarse-shaded fragments. Runtime choices are in Game settings. Tested on RE4, Deadpool and Asgard's Wrath 2. The Climb 2 gets a version-guarded private bridge at 1.0 scale; Batman gets its hash-verified allocation fix (unsupported OVRPlugin versions are refused). Other games may need additional fixes; turn this off if artifacts appear.
|
||||
|
||||
### <a id="device-headset_model"></a>Headset model
|
||||
|
||||
@@ -2532,7 +2535,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 (114):** AFFECTED: The Manor, AgeOfJoy, AllInOneSports, Ancient_Dungeon, Arcsmith, Arkio, Assassin's Creed Nexus, Attack on Titan VR: Unbreakable and 106 more.
|
||||
- **Catalog games (116):** AFFECTED: The Manor, AgeOfJoy, AllInOneSports, Ancient_Dungeon, Arcsmith, Arkio, Assassin's Creed Nexus, Attack on Titan VR: Unbreakable and 108 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".
|
||||
|
||||
@@ -20,6 +20,7 @@ version is `catalog/triage.yaml` (used by `frameport test` / the Job screen); ke
|
||||
| Conversion fails with `NoSuchElementException: Sequence is empty` in `PatcherContext.appName` (GitHub #269, Accounting+) | OVRPort assumes an application label exists; some APKs only label their activity | FramePort retries that exact failure once using a temporary APK whose application inherits the activity's label; existing labels and the original APK are kept |
|
||||
| Updating a flat Android app after saving its recipe fails with `no OVRPort patches selected` (GitHub #271, Plezy) | older FramePort loads the saved recipe without restoring its unchanged-install setting or applying the flat-app build rules, then tries an empty VR conversion | update FramePort: saved recipes keep `as_is`, and flat apps still skip OVRPort; existing catalog-derived recipes refresh at the next app update. Until then, enable "Install as is" for an app whose APK needs no fixes |
|
||||
| A launch test suggests a patch (for example triage `sdl-no-clipboard` → `frame.sdl_clipboard`) but the game page doesn't show it / lists it as not applicable; or a game added long ago lacks warnings newer ones get (Android version, web wrapper, missing OBB) (GitHub #104, Dramatic Shape) | the library entry was analyzed by an older FramePort: the analysis field the patch reads (`sdl_java`, `min_sdk`, `web_wrapper`, `expects_obb`, …) is missing, so `applies()` is false | entries whose `analysis.extra.analysis_version` is below `detect.ANALYSIS_VERSION` are analyzed again at the GUI's start (background) and before a build (`pipeline.refresh_analyses`); by hand: game menu → "Analyze again", then Update on Frame. An unreadable APK is marked `analysis_failed` (retried after the next version bump). Adding an analysis field: bump `ANALYSIS_VERSION` |
|
||||
| Foveation full-resolution area looks identical at 10% and 45% (GitHub #394), while render resolution changes it | `FOVE_INNER`/`FOVE_OUTER` are neither passed through by Lepton nor read by Valve's layer; shared injection uses rectangular density presets | Game settings → Picture now exports named `FOVE_LEVEL=lo/med/hi`; Default preserves the runtime choice. Medium/high shrink the center and reduce peripheral density further. Legacy radius preferences map to the nearest old nominal preset. Compare Fixed + Darken coarse fragments with adapter `snapshot=N` at a fixed render resolution; see FRAME_RUNTIME.md for addresses and profile data. Agent launcher allowlist needs `FOVE_LEVEL`. |
|
||||
| FDM layer loads but diagnostic logging reports `fdm_injection_CreateInstance: skipped` | Layer loading alone does not enable injection on the tested SteamOS 0.4.5 build; a skipped helper instance alone also does not establish game-wide inactivity | Opt-in `device.foveation` mode `tracked`: loads Valve's layer and sets `FDM_DEBUG=enable`. Batman probe reached foveated render passes with changing per-eye offsets. Validate graphics and matched gameplay GPU time; do not enable globally. Use `debug` only for brief diagnostics, preserving existing flags; never benchmark with debug spam. See FRAME_RUNTIME.md. |
|
||||
| One eye (usually the right) shimmers or jitters in the 3D view while menus and panels look steady; pacing is clean (GitHub #69, e.g. RTCWQuest, Moss) | Valve's eye-tracked foveation layer (`VALVE_fdm_injection`) moves its low-density regions with each eye's gaze, and the right eye's gaze filter restarts often (`CEyePoseUKF R: Large dt … Bootstrapped` in `~/.local/share/Steam/logs/eyetracking.txt`) | Game settings → Picture → "Foveated rendering (Valve)": **Fixed** (`FDM_DEBUG=disable_offsets`; verify injection remains active on the installed runtime) or **Off** (`VK_INSTANCE_LAYERS=""`); catalog field `foveation:` |
|
||||
| With `device.foveation` Eye-tracked/Fixed, the sharp area looks right but everything around it is black with scattered coloured blocks (menus on quads too); pacing fine (GitHub #303, Medieval Dynasty; Vampire Survivors reported the same) | Unity's Oculus XR plugin asks for SUBSAMPLED eye images (`XR_META_vulkan_swapchain_create_info`, `VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT`); under Valve's FDM their coarse areas stay in the compact subsampled layout, which the compositor reads as an ordinary image. `foveation_fix=1` only dropped the `XrSwapchainCreateInfoFoveationFB` nodes and passed the META node on. With `foveation_fix=0` + `foveation_storage_fix=1` the adapter logs `foveation_storage_fix: shared allocation SUBSAMPLED cleared` for the eye swapchains | `device.foveation` (tracked/fixed) now also sets `foveation_storage_fix=1`, which clears SUBSAMPLED whatever `foveation_fix` is (generic version of Batman's OVRPlugin allocation fix). Rebuild + reinstall (new adapter). Headless-checked only (72 fps, bit cleared); picture needs the headset |
|
||||
|
||||
@@ -56,7 +56,7 @@ def from_data() -> dict:
|
||||
for t in (p.title, p.description, p.summary):
|
||||
if t:
|
||||
texts[t] = ""
|
||||
for attr in ("CHOICES", "RADIUS_CHOICES"):
|
||||
for attr in ("CHOICES", "LEVEL_CHOICES"):
|
||||
for _, label in getattr(p, attr, ()):
|
||||
texts[label] = ""
|
||||
for _, title in GROUPS: # the Game settings dialog
|
||||
|
||||
@@ -39,17 +39,11 @@
|
||||
", {n} screenshots": "",
|
||||
"0 = the size of your play area.": "",
|
||||
"10 games on the Frame. It's getting cozy in there.": "",
|
||||
"10% radius": "",
|
||||
"100 games on the Frame. The Frame thanks you.": "",
|
||||
"108 Hz": "",
|
||||
"120 Hz": "",
|
||||
"144 Hz": "",
|
||||
"15% radius": "",
|
||||
"20% radius": "",
|
||||
"25% radius": "",
|
||||
"30% radius": "",
|
||||
"32-bit ARM bridge (Gear VR / Go games)": "",
|
||||
"35% radius": "",
|
||||
"360p (lightest)": "",
|
||||
"360° (equirect) layers, for example a video player's virtual theater or 360° videos (for example 4XVR), which the Frame's runtime lacks, are drawn by a background thread into a layer behind the game's own picture: the 360° image is converted only when it changes (a theater once, a 360° video once per video frame) and the view is drawn for each frame's head pose. GLES games only; without it these layers are missing (black).": "",
|
||||
"360° 3D": "",
|
||||
@@ -58,8 +52,6 @@
|
||||
"360° pictures and video": "",
|
||||
"360° sharpness": "",
|
||||
"360° video frame rate limit": "",
|
||||
"40% radius": "",
|
||||
"45% radius": "",
|
||||
"72 Hz": "",
|
||||
"720p (recommended)": "",
|
||||
"80 Hz": "",
|
||||
@@ -392,7 +384,7 @@
|
||||
"Declares Meta's permissions some mixed-reality games ask for, so they don't stop.": "",
|
||||
"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.": "",
|
||||
"Default": "",
|
||||
"Default (30% radius)": "",
|
||||
"Default (large)": "",
|
||||
"Delete": "",
|
||||
"Delete {names}?": "",
|
||||
"Delete {n} items…": "",
|
||||
@@ -864,6 +856,7 @@
|
||||
"kshoji's USB MIDI driver (Unity MIDI plugins, for example PianoVision) polls Android's USB service for MIDI devices; the Frame's Android has no USB service, so the app crashes a few seconds after start (NullPointerException on MidiDeviceConnectionWatchThread). The poll returns at once instead, so no USB MIDI device is ever found (an in-place edit of classes.dex).": "",
|
||||
"Language": "",
|
||||
"Language packs from the game's files": "",
|
||||
"Large": "",
|
||||
"Large files upload together using OpenSSH when available. Default: 3. Lower this on a busy network.": "",
|
||||
"Larger areas retain more detail but save less GPU work.": "",
|
||||
"Last build": "",
|
||||
@@ -977,6 +970,7 @@
|
||||
"Marks the game as a VR app so headsets show it as one.": "",
|
||||
"Maximum": "",
|
||||
"Maximum size in pixels of each face of the cube the 360° image is converted to (256–2730). Higher is sharper but uses more GPU memory.": "",
|
||||
"Medium (saves more)": "",
|
||||
"Memory": "",
|
||||
"Memory used by everything on the Frame. \"Waiting\" is how often programs waited for memory in the last 10 s; when it climbs, the Frame may freeze or close a game.": "",
|
||||
"Menus and screens": "",
|
||||
@@ -1301,7 +1295,7 @@
|
||||
"Reconnect": "",
|
||||
"Recreate spaces after the first frames (diagnostic).": "",
|
||||
"Reduce motion": "",
|
||||
"Reduces shading away from the full-resolution area, following each eye's gaze. Default area size keeps the runtime's current profile unchanged. Custom radius is a fraction of the shorter eye-image dimension; the transition ends at twice that radius. Larger areas retain more detail but save less GPU work. Fixed stops the area following your eyes. The optional test darkens actual coarse-shaded fragments. Runtime choices are in Game settings. Tested on RE4, Deadpool and Asgard's Wrath 2. The Climb 2 gets a version-guarded private bridge at 1.0 scale; Batman gets its hash-verified allocation fix (unsupported OVRPlugin versions are refused). Other games may need additional fixes; turn this off if artifacts appear.": "",
|
||||
"Reduces shading away from the full-resolution area, following each eye's gaze. Default keeps the runtime profile unchanged. Low has the largest full-resolution center; medium and high use smaller centers and stronger peripheral detail reduction. Larger areas retain more detail but save less GPU work. Fixed stops the area following your eyes. The optional test darkens actual coarse-shaded fragments. Runtime choices are in Game settings. Tested on RE4, Deadpool and Asgard's Wrath 2. The Climb 2 gets a version-guarded private bridge at 1.0 scale; Batman gets its hash-verified allocation fix (unsupported OVRPlugin versions are refused). Other games may need additional fixes; turn this off if artifacts appear.": "",
|
||||
"Reduces shading away from the full-resolution area. Changes apply at the next game start.": "",
|
||||
"Refresh": "",
|
||||
"Refresh rate": "",
|
||||
@@ -1522,6 +1516,7 @@
|
||||
"Skips Meta XR Audio's telemetry under x86_64 ARM translation (emulators). Not needed on the Frame, which runs games natively.": "",
|
||||
"Skips picture layers the Frame would refuse. Leave it on.": "",
|
||||
"Sleep": "",
|
||||
"Small (saves most)": "",
|
||||
"Smooth as butter": "",
|
||||
"software encoder": "",
|
||||
"Some apps open a flat Android launcher that then starts a separate VR activity (for example WiiCompiled). Shown with Lepton's flat window, that window stays in view after the VR part starts. This makes the VR activity the one Lepton starts (its LAUNCHER category), so the app opens in VR with no flat window. Everything only the launcher does (choosing or importing content) is skipped: turn it off for a run when you need the launcher, and turn off \"Show the app's Android window\".": "",
|
||||
|
||||
@@ -152,8 +152,9 @@ class UnrealNoReserve(Patch):
|
||||
if not lib or ws.abi != "arm64-v8a":
|
||||
return []
|
||||
todo, done = mmap_sites(ws.read_lib(lib))
|
||||
return [("Unreal memory reservations use MAP_NORESERVE", bool(done) and not todo,
|
||||
f"{len(done)} rewritten, {len(todo)} left")]
|
||||
# nothing to rewrite (an engine build without these reservations, e.g. Lies Beneath, GitHub #393) passes
|
||||
return [("Unreal memory reservations use MAP_NORESERVE", not todo,
|
||||
f"{len(done)} rewritten, {len(todo)} left" if todo or done else "none in this build")]
|
||||
|
||||
|
||||
register(UnrealNoReserve)
|
||||
@@ -300,7 +300,7 @@ REFRESH_CHOICES = [(0.0, "Game's choice"), *((float(hz), f"{hz} Hz") for hz in (
|
||||
FOVEATION_UI = {
|
||||
"mode": dict(group="picture", level="common", label="Foveated rendering (Valve)",
|
||||
help="Reduces shading away from the full-resolution area. Changes apply at the next game start."),
|
||||
"radius": dict(group="picture", level="common", label="Full-resolution area",
|
||||
"level": dict(group="picture", level="common", label="Full-resolution area",
|
||||
help="Larger areas retain more detail but save less GPU work."),
|
||||
"test": dict(group="picture", level="advanced", label="Darken coarse fragments (test)",
|
||||
help="Diagnostic only; do not use for benchmarking."),
|
||||
@@ -809,23 +809,33 @@ def foveation_options(params):
|
||||
mode = params.get("mode") or ""
|
||||
if mode not in ("", "tracked", "fixed", "off"):
|
||||
raise ValueError("Unknown foveation mode")
|
||||
radius = float(params.get("radius") or 0)
|
||||
if not math.isfinite(radius) or (radius != 0 and not 0.05 <= radius <= 0.45):
|
||||
raise ValueError("Full-resolution radius must be Default (0), or 0.05–0.45")
|
||||
level = params.get("level", "")
|
||||
if "level" not in params and params.get("radius"):
|
||||
# Old radius exports never reached the layer. Preserve intent with a supported
|
||||
# preset; unknown legacy values restore Default. No numeric FOVE_LEVEL values.
|
||||
try:
|
||||
radius = float(params["radius"])
|
||||
except (ValueError, TypeError):
|
||||
radius = 0
|
||||
if math.isfinite(radius) and 0.05 <= radius <= 0.45:
|
||||
level = min(((.30, "lo"), (.25, "med"), (.20, "hi")),
|
||||
key=lambda pair: abs(pair[0] - radius))[1]
|
||||
if level not in ("", "lo", "med", "hi"):
|
||||
raise ValueError("Unknown foveation profile")
|
||||
test = params.get("test", False)
|
||||
if not isinstance(test, bool):
|
||||
raise ValueError("Foveation test mode must be a boolean")
|
||||
if (test or radius) and mode not in ("tracked", "fixed"):
|
||||
if (test or level) and mode not in ("tracked", "fixed"):
|
||||
raise ValueError("Choose Eye-tracked or Fixed before configuring foveation size or test mode")
|
||||
return mode, radius, test
|
||||
return mode, level, test
|
||||
|
||||
|
||||
class Foveation(Patch):
|
||||
id = "device.foveation"
|
||||
title = "Foveated rendering (Valve)"
|
||||
description = ("Reduces shading away from the full-resolution area, following each eye's gaze. "
|
||||
"Default area size keeps the runtime's current profile unchanged. Custom radius is a fraction "
|
||||
"of the shorter eye-image dimension; the transition ends at twice that radius. Larger areas "
|
||||
"Default keeps the runtime profile unchanged. Low has the largest full-resolution center; medium and high "
|
||||
"use smaller centers and stronger peripheral detail reduction. Larger areas "
|
||||
"retain more detail but save less GPU work. Fixed stops the area following your eyes. "
|
||||
"The optional test darkens actual coarse-shaded fragments. Runtime choices are in Game settings. "
|
||||
"Tested on RE4, Deadpool and Asgard's Wrath 2. "
|
||||
@@ -840,13 +850,13 @@ class Foveation(Patch):
|
||||
experimental = True
|
||||
params = [Param("support", "bool", False, "Internal APK compatibility support"),
|
||||
Param("mode", "str", "", "Catalog default: tracked, fixed or off (empty = Valve's default)"),
|
||||
Param("radius", "float", 0.0, "Full-resolution radius; 0 keeps the current runtime default", 0, 0.45),
|
||||
Param("level", "str", "", "Runtime profile: lo, med, hi (empty = Default)"),
|
||||
Param("test", "bool", False, "Darken lower-resolution regions (diagnostic)")]
|
||||
CHOICES = (("", "Default"), ("tracked", "Eye-tracked"),
|
||||
("fixed", "Fixed (doesn't follow the eyes)"), ("off", "Off"))
|
||||
RADIUS_CHOICES = (("0", "Default (30% radius)"), ("0.1", "10% radius"), ("0.15", "15% radius"),
|
||||
("0.2", "20% radius"), ("0.25", "25% radius"), ("0.3", "30% radius"),
|
||||
("0.35", "35% radius"), ("0.4", "40% radius"), ("0.45", "45% radius"))
|
||||
# Valve's layer starts at lo when FOVE_LEVEL is unset, so Default is the large area
|
||||
LEVEL_CHOICES = (("", "Default (large)"), ("lo", "Large"), ("med", "Medium (saves more)"),
|
||||
("hi", "Small (saves most)"))
|
||||
|
||||
def apply(self, ctx):
|
||||
from .frame.foveation import apply
|
||||
@@ -858,11 +868,13 @@ class Foveation(Patch):
|
||||
|
||||
def install(self, ctx: InstallContext) -> None:
|
||||
# runs after device.lepton_env (install_context), so this choice wins over a recipe's VK_INSTANCE_LAYERS
|
||||
mode, radius, test = foveation_options(ctx.params)
|
||||
mode, level, test = foveation_options(ctx.params)
|
||||
ctx.env.pop("FOVE_INNER", None)
|
||||
ctx.env.pop("FOVE_OUTER", None)
|
||||
if not mode:
|
||||
return
|
||||
for key in ("FDM_DEBUG", "ENABLE_VULKAN_FDM_INJECTION_LAYER",
|
||||
"LEPTON_ENV_DISABLE_VULKAN_FDM_INJECTION_LAYER", "FOVE_INNER", "FOVE_OUTER",
|
||||
"LEPTON_ENV_DISABLE_VULKAN_FDM_INJECTION_LAYER", "FOVE_INNER", "FOVE_OUTER", "FOVE_LEVEL",
|
||||
"LEPTON_ENV_FRAMEPORT_FOVEATION_TEST", "LEPTON_ENV_FRAMEPORT_CLIMB_STEREO_OFFSETS",
|
||||
"LEPTON_ENV_FRAMEPORT_VRAPI_FDM_ISOLATE_MONO",
|
||||
"LEPTON_ENV_FRAMEPORT_VRAPI_KEEP_FOCUSED_IMAGE"):
|
||||
@@ -881,8 +893,8 @@ class Foveation(Patch):
|
||||
ctx.env.update(LEPTON_ENV_FRAMEPORT_CLIMB_STEREO_OFFSETS="1",
|
||||
LEPTON_ENV_FRAMEPORT_VRAPI_FDM_ISOLATE_MONO="1",
|
||||
LEPTON_ENV_FRAMEPORT_VRAPI_KEEP_FOCUSED_IMAGE="1")
|
||||
if radius:
|
||||
ctx.env.update(FOVE_INNER=f"{radius:g}", FOVE_OUTER=f"{2 * radius:g}")
|
||||
if level:
|
||||
ctx.env["FOVE_LEVEL"] = level
|
||||
if test:
|
||||
ctx.env["LEPTON_ENV_FRAMEPORT_FOVEATION_TEST"] = "1"
|
||||
|
||||
@@ -944,16 +956,20 @@ register(Foveation)
|
||||
|
||||
|
||||
FOVEATION_ENV_KEYS = ("FDM_DEBUG", "ENABLE_VULKAN_FDM_INJECTION_LAYER",
|
||||
"LEPTON_ENV_DISABLE_VULKAN_FDM_INJECTION_LAYER", "VK_INSTANCE_LAYERS", "FOVE_INNER", "FOVE_OUTER",
|
||||
"LEPTON_ENV_DISABLE_VULKAN_FDM_INJECTION_LAYER", "VK_INSTANCE_LAYERS", "FOVE_INNER", "FOVE_OUTER", "FOVE_LEVEL",
|
||||
"LEPTON_ENV_FRAMEPORT_FOVEATION_TEST", "LEPTON_ENV_FRAMEPORT_CLIMB_STEREO_OFFSETS",
|
||||
"LEPTON_ENV_FRAMEPORT_VRAPI_FDM_ISOLATE_MONO", "LEPTON_ENV_FRAMEPORT_VRAPI_KEEP_FOCUSED_IMAGE")
|
||||
|
||||
|
||||
def foveation_preference(entry):
|
||||
if "foveation" in entry:
|
||||
return dict(entry["foveation"])
|
||||
params = ((entry.get("recipe") or {}).get("patches") or {}).get("device.foveation") or {}
|
||||
return {k: v for k, v in params.items() if k in ("mode", "radius", "test")}
|
||||
params = entry.get("foveation") if "foveation" in entry else (
|
||||
((entry.get("recipe") or {}).get("patches") or {}).get("device.foveation") or {})
|
||||
pref = {k: v for k, v in params.items() if k in ("mode", "level", "radius", "test")}
|
||||
if "radius" in pref:
|
||||
_, level, _ = foveation_options(pref)
|
||||
pref.pop("radius")
|
||||
pref["level"] = level
|
||||
return pref
|
||||
|
||||
|
||||
def foveation_env(package, params, settings=None, baseline=None):
|
||||
|
||||
@@ -103,7 +103,7 @@ def show_adapter_dialog(app: FramePortApp, package: str) -> None:
|
||||
if env.get("ENABLE_VULKAN_FDM_INJECTION_LAYER") == "1" else "")
|
||||
pref = {"mode": mode}
|
||||
if mode in ("tracked", "fixed"):
|
||||
pref.update(radius=float(env.get("FOVE_INNER") or 0),
|
||||
pref.update(level=env.get("FOVE_LEVEL") if env.get("FOVE_LEVEL") in ("lo", "med", "hi") else "",
|
||||
test=env.get("LEPTON_ENV_FRAMEPORT_FOVEATION_TEST") == "1")
|
||||
_open(app, package, None, vals, pref)
|
||||
app.run_bg(load)
|
||||
@@ -122,24 +122,23 @@ def _open(app: FramePortApp, package: str, g: dict | None, values: dict, foveati
|
||||
|
||||
def foveation_change(key, value):
|
||||
foveation[key] = value
|
||||
for name in ("radius", "test"):
|
||||
for name in ("level", "test"):
|
||||
if name in foveation_rows:
|
||||
foveation_rows[name].visible = (foveation.get("mode") in ("tracked", "fixed") and
|
||||
(name != "test" or advanced["on"]))
|
||||
C.update(foveation_rows[name])
|
||||
|
||||
foveation_specs = (("mode", Foveation.CHOICES), ("radius", Foveation.RADIUS_CHOICES), ("test", None))
|
||||
foveation_specs = (("mode", Foveation.CHOICES), ("level", Foveation.LEVEL_CHOICES), ("test", None))
|
||||
for key, choices in (foveation_specs if not g or g.get("kind", "quest") == "quest" else ()):
|
||||
ui = FOVEATION_UI[key]
|
||||
if choices:
|
||||
value = str(foveation.get(key, "")) if key == "mode" else f"{float(foveation.get(key) or 0):g}"
|
||||
value = str(foveation.get(key, ""))
|
||||
choices = list(choices)
|
||||
if value not in [str(v) for v, _ in choices]:
|
||||
choices.append((value, value))
|
||||
control = C.dropdown(value=value, width=T.px(220),
|
||||
options=[ft.DropdownOption(key=str(v), text=tr(label)) for v, label in choices],
|
||||
on_select=lambda e, k=key: foveation_change(k, float(e.control.value) if k == "radius"
|
||||
else e.control.value))
|
||||
on_select=lambda e, k=key: foveation_change(k, e.control.value))
|
||||
else:
|
||||
control = C.switch(value=bool(foveation.get(key)),
|
||||
on_change=lambda e: foveation_change("test", bool(e.control.value)))
|
||||
@@ -232,7 +231,7 @@ def _open(app: FramePortApp, package: str, g: dict | None, values: dict, foveati
|
||||
|
||||
def save(e):
|
||||
if foveation.get("mode", "") not in ("tracked", "fixed"):
|
||||
foveation.pop("radius", None)
|
||||
foveation.pop("level", None)
|
||||
foveation.pop("test", None)
|
||||
foveation_options(foveation)
|
||||
app.page.pop_dialog()
|
||||
|
||||
@@ -45,8 +45,8 @@ def test_injection_keeps_swapchains_ordinary():
|
||||
def test_custom_size_and_fixed_mode_and_reset():
|
||||
assert context({"mode": "tracked", "test": True}).env["LEPTON_ENV_FRAMEPORT_FOVEATION_TEST"] == "1"
|
||||
assert "LEPTON_ENV_FRAMEPORT_FOVEATION_TEST" not in context({"mode": "tracked"}).env
|
||||
assert context({"mode": "tracked", "radius": 0.35}).env["FOVE_INNER"] == "0.35"
|
||||
assert context({"mode": "tracked", "radius": 0.35}).env["FOVE_OUTER"] == "0.7"
|
||||
assert context({"mode": "tracked", "radius": 0.35}).env["FOVE_LEVEL"] == "lo"
|
||||
assert context({"mode": "tracked", "level": "hi"}).env["FOVE_LEVEL"] == "hi"
|
||||
assert context({"mode": "fixed"}).env["FDM_DEBUG"] == "enable,disable_offsets"
|
||||
assert context({"mode": "off"}).env == {
|
||||
"VK_INSTANCE_LAYERS": "", "LEPTON_ENV_DISABLE_VULKAN_FDM_INJECTION_LAYER": "1"}
|
||||
@@ -57,9 +57,8 @@ def test_custom_size_and_fixed_mode_and_reset():
|
||||
|
||||
@pytest.mark.parametrize("params", [
|
||||
{"mode": "bad"}, {"mode": "off", "test": True}, {"mode": "", "radius": 0.2},
|
||||
{"mode": "tracked", "radius": float("nan")}, {"mode": "tracked", "radius": float("inf")},
|
||||
{"mode": "tracked", "radius": -1}, {"mode": "tracked", "radius": 0.01},
|
||||
{"mode": "tracked", "radius": 0.5}, {"mode": "tracked", "test": "false"}])
|
||||
{"mode": "tracked", "level": "1"}, {"mode": "tracked", "level": 2},
|
||||
{"mode": "off", "level": "lo"}, {"mode": "tracked", "test": "false"}])
|
||||
def test_invalid_configuration_refused(params):
|
||||
with pytest.raises(ValueError):
|
||||
foveation_options(params)
|
||||
|
||||
@@ -87,10 +87,11 @@ def test_catalog_defaults_survive_and_explicit_choice_wins():
|
||||
|
||||
def test_radius_test_and_off_reset_managed_exports():
|
||||
env = foveation_env("test.game", {"mode": "fixed", "radius": .2, "test": True})
|
||||
assert env["FOVE_INNER"] == "0.2" and env["FOVE_OUTER"] == "0.4"
|
||||
assert env["FOVE_LEVEL"] == "hi"
|
||||
assert env["FOVE_INNER"] is None and env["FOVE_OUTER"] is None
|
||||
assert env["LEPTON_ENV_FRAMEPORT_FOVEATION_TEST"] == "1"
|
||||
off = foveation_env("test.game", {"mode": "off"}, baseline={k: v for k, v in env.items() if v})
|
||||
assert off["FOVE_INNER"] is None and off["LEPTON_ENV_FRAMEPORT_FOVEATION_TEST"] is None
|
||||
assert off["FOVE_LEVEL"] is None and off["LEPTON_ENV_FRAMEPORT_FOVEATION_TEST"] is None
|
||||
|
||||
|
||||
def test_resolution_label_explains_actual_effect():
|
||||
@@ -129,8 +130,9 @@ def test_game_dialog_saves_personal_choice_without_touching_recipe(monkeypatch):
|
||||
any(o.key == "tracked" for o in c.options))
|
||||
mode.on_select(SimpleNamespace(control=SimpleNamespace(value="fixed")))
|
||||
radius = next(c for c in controls if isinstance(c, ft.Dropdown) and
|
||||
any(o.key == "0.3" for o in c.options))
|
||||
radius.on_select(SimpleNamespace(control=SimpleNamespace(value="0.3")))
|
||||
any(o.key == "med" for o in c.options))
|
||||
assert [(o.key, o.text) for o in radius.options] == list(ad.Foveation.LEVEL_CHOICES)
|
||||
radius.on_select(SimpleNamespace(control=SimpleNamespace(value="med")))
|
||||
advanced_row = next(c for c in controls if isinstance(c, ft.Row) and c.data == "switch" and
|
||||
any(getattr(t, "value", None) == "Show advanced settings" for t in walk(c)))
|
||||
advanced = next(c for c in walk(advanced_row) if isinstance(c, ft.Switch))
|
||||
@@ -145,7 +147,7 @@ def test_game_dialog_saves_personal_choice_without_touching_recipe(monkeypatch):
|
||||
save = dialogs[-1].actions[-1]
|
||||
save.on_click(None)
|
||||
saved = library.game("test.game")
|
||||
assert saved["foveation"] == {"mode": "fixed", "radius": .3, "test": True}
|
||||
assert saved["foveation"] == {"mode": "fixed", "level": "med", "test": True}
|
||||
assert saved["recipe"] == game["recipe"]
|
||||
ad._open(app, "test.game", saved, ad.recipe_values(saved))
|
||||
mode = next(c for c in walk(dialogs[-1]) if isinstance(c, ft.Dropdown) and
|
||||
@@ -160,3 +162,42 @@ def test_adapter_build_prepares_internal_foveation_support():
|
||||
from frameport.build import apk_patches
|
||||
assert "device.foveation" in [p.id for p in apk_patches({"frame.adapter": {}})]
|
||||
assert "device.foveation" not in [p.id for p in apk_patches({"frame.launcher": {}})]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("radius,level", [(0, ""), (.1, "hi"), (.2, "hi"), (.25, "med"),
|
||||
(.3, "lo"), (.45, "lo"), ("unknown", ""), (float("nan"), ""), (.9, "")])
|
||||
def test_legacy_radius_read_migrates_without_mutating_storage(radius, level):
|
||||
entry = {"foveation": {"mode": "tracked", "radius": radius}}
|
||||
before = dict(entry["foveation"])
|
||||
assert foveation_preference(entry) == {"mode": "tracked", "level": level}
|
||||
assert entry["foveation"] == before
|
||||
env = foveation_env("test.game", entry["foveation"])
|
||||
assert env["FOVE_LEVEL"] == (level or None)
|
||||
assert env["FOVE_INNER"] is None and env["FOVE_OUTER"] is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("level", ["", "lo", "med", "hi"])
|
||||
def test_supported_level_exports_and_explicit_level_wins(level):
|
||||
env = foveation_env("test.game", {"mode": "tracked", "level": level, "radius": .2})
|
||||
assert env["FOVE_LEVEL"] == (level or None)
|
||||
assert env["FDM_DEBUG"] == "enable"
|
||||
|
||||
|
||||
def test_default_removes_obsolete_radius_exports_and_legacy_recipe_reads():
|
||||
env = foveation_env("test.game", {"mode": ""}, baseline={"FOVE_INNER": ".4", "FOVE_OUTER": ".8"})
|
||||
assert env["FOVE_INNER"] is None and env["FOVE_OUTER"] is None
|
||||
entry = {"recipe": {"patches": {"device.foveation": {"mode": "fixed", "radius": .25}}}}
|
||||
assert foveation_preference(entry) == {"mode": "fixed", "level": "med"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("level,expected", [("hi", "hi"), ("1", ""), (None, "")])
|
||||
def test_installed_only_dialog_reads_supported_level(monkeypatch, level, expected):
|
||||
from frameport.ui.views import adapter_dialog as ad
|
||||
monkeypatch.setattr(library, "game", lambda package: None)
|
||||
received = []
|
||||
monkeypatch.setattr(ad, "_open", lambda *args: received.append(args[-1]))
|
||||
target = SimpleNamespace(set_settings=lambda *args: {"settings": {}, "foveation_env": {
|
||||
"ENABLE_VULKAN_FDM_INJECTION_LAYER": "1", "FDM_DEBUG": "enable", "FOVE_LEVEL": level}})
|
||||
app = SimpleNamespace(target=target, run_bg=lambda fn: fn())
|
||||
ad.show_adapter_dialog(app, "test.game")
|
||||
assert received == [{"mode": "tracked", "level": expected, "test": False}]
|
||||
@@ -64,6 +64,9 @@ def test_other_mmap_calls_are_left_alone(tmp_path, kwargs):
|
||||
ctx = context(tmp_path, data)
|
||||
with ctx.ws:
|
||||
assert not nr.UnrealNoReserve().apply(ctx)
|
||||
# nothing to rewrite is not a failed build (GitHub #393: Lies Beneath failed "0 rewritten, 0 left")
|
||||
name, ok, detail = nr.UnrealNoReserve().validate(ctx)[0]
|
||||
assert ok and detail == "none in this build"
|
||||
|
||||
|
||||
def test_call_to_another_function_is_left_alone(monkeypatch):
|
||||
|
||||
Reference in new issue
Block a user