From ef21b33ca28e46ebbea84ea804527b2c2e966ec9 Mon Sep 17 00:00:00 2001 From: spoopyghosty0 <336754034+spoopyghosty0@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:30:07 -0400 Subject: [PATCH] Vulkan shim: vk_query_slots (per game) gives occlusion queries one slot per eye; in multiview passes the Frame's Mesa driver writes the second eye's zero result into the next query, culling visible objects (Into The Radius 2's popping models; not yet confirmed in a headset). Sidebar: Sleep / Restart / Shut down below the Frame card --- CLAUDE.md | 2 +- artifacts/SHA256SUMS | 2 +- artifacts/arm64-v8a/libfp_vk.so | Bin 19352 -> 27504 bytes native/vkshim/vkshim.c | 192 +++++++++++++++++++++++++++- src/frameport/locales/template.json | 2 + src/frameport/patches/settings.py | 9 +- src/frameport/ui/app.py | 2 +- 7 files changed, 202 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5bb92cf..ce5d22d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -520,7 +520,7 @@ Steam placeholder set (no Steam art at all) → fixed in artwork/steam.py. **Space warp / ITR2 (2026-10-04):** Into The Radius 2 (UE5, `libUnreal.so` + OVRPlugin, Vulkan) uses Application SpaceWarp (extra swapchains `376x376 format=97` motion vectors + `format=129` depth); #35 reports flickering textures. OVRPort's `patch_disable_space_warp` has no effect on UE5, and hiding XR_FB_space_warp in FrameBridge's enumerate doesn't reach the game either: **OVRPort's dispatcher offers/enables XR_FB_space_warp itself**. FrameBridge `hide_space_warp` (per game, off) hides it and strips `XrCompositionLayerSpaceWarpInfoFB` (1000171000) from the projection views (log `hide_space_warp: removed space warp info`); the game still renders its MV swapchains. Test build installed on the dev Frame; flicker not yet checked in a headset. Heuristic: UE5 + OVRPlugin shows it as an option (off). `equirect_emul` is suggested for Unreal games whose graphics API isn't detected ("GLES or unknown"); on Vulkan it switches itself off (Myst/Riven/ITR2): harmless, but Unreal Vulkan detection is a known gap. **PR #34 (merged 2026-10-04, Lucas-Mathieu):** `native/adapter/audio_metadata.h` patches Meta XR Audio Wwise (only build ID e1619e7f…, Batman: Arkham Shadow 1.4.1) so queued audio metadata isn't freed while the current audio frame references it (smoke-bomb crash). Verified headless: installs ("metadata reclamation follows …"), 72 fps; we added a once-per-second scan limit. Outside PRs: review source, rebuild `artifacts/` ourselves, merge locally (contributor's commit kept), push main. **Heuristics eval (2026-10-04):** `scripts/eval_heuristics.py ""` → 19/44 recipes exact, 322/352 fields (most diffs: `frame.unity_text_input` suggested where older catalog recipes lack it; the rest are runtime-only findings: use_alt, vk_shader_fix, focus_hold, sync_guard, multipass). `PATCHED/` copies aren't scored (already converted). -**Power (0.9, agent v47):** sidebar bar above the Frame card: Sleep / Restart / Shut down, each confirmed (`app.frame_power`, a running game asks again). logind answers CanSuspend/CanReboot/CanPowerOff = "challenge" for an SSH session but "yes" inside a user unit, so agent `power` runs `systemctl suspend|reboot|poweroff` from a transient `systemd-run --user --on-active=3` timer (the command answers first). Restart verified on the device (back in ~84 s, new boot id). +**Power (0.9, agent v47):** sidebar bar below the Frame card: Sleep / Restart / Shut down, each confirmed (`app.frame_power`, a running game asks again). logind answers CanSuspend/CanReboot/CanPowerOff = "challenge" for an SSH session but "yes" inside a user unit, so agent `power` runs `systemctl suspend|reboot|poweroff` from a transient `systemd-run --user --on-active=3` timer (the command answers first). Restart verified on the device (back in ~84 s, new boot id). **Proton default (agent v51, owner's choice 2026-10-05):** the newest **stable** ARM64 Proton is the default for PC VR games (`pick_proton`: asked-for name/alias, else newest installed stable, else any stable). Agent v49-50 defaulted to Experimental; the owner's headset A/B with Rick and Morty (Experimental 69 % reprojected frames, stable felt much smoother) reverted it. The game page's Customize shows `pcvr.proton_tool` as a dropdown (Stable (default) / Experimental = alias `proton-experimental`); `ensure_proton` installs a chosen one (it asks once more for the runtime Steam only names after Proton is installed; Experimental installed unattended on the device in 53 s). A PC VR launch test that fails on stable suggests Experimental (`pipeline.PROTON_TOOL`, `proton_alternative_worth_trying`, button "Try Proton Experimental and reinstall"). Remaining Rick and Morty stutter on head turns = the game rendering below the refresh rate (reprojection), not Proton. **USB cable link (verified 2026-10-05, cabled to the dev PC):** `usb0` is a USB **NCM** gadget (configfs g1: `ncm.usb0` + `ffs.adb`, Valve 28de:2460) set up by `usb-ncm-gadget@usb0.service`, which only runs with `ConditionPathExists=|/etc/systemd/system/adbd.service.d/steamos-devkit-enabled` = **Developer Mode only**. `usb-ncm-dnsmasq@usb0` serves DHCP with fixed `dhcp-host` entries: Frame 10.86.200.233, PC **10.86.200.234** (`frame/usb.py` FRAME_USB_IP/PC_USB_IP). Windows 11 binds its built-in "UsbNcm Host Device" (no driver); WSL mirrored sees eth 10.86.200.234. USB is high-speed (USB 2.0): upload 36.9 MB/s vs 11.3 MB/s home Wi-Fi. Frame→PC over the cable worked without a firewall change. `scripts/usb_autotest.py` (Windows via powershell.exe from WSL), `frameport frame usb-check`. Wizard "Set up with a USB cable" (`PairingServer(host=PC_USB_IP)`; the real bootstrap ran end to end over USB). Discovery dedupes by SSH host key and prefers USB > Frame hotspot > network (`discovery.dedupe`); a lost address is re-found by key (`Frame.relocate`, tested with a dead saved address). **Catalog updates without a release (2026-10-05):** apps fetch `catalog/games/*.yaml` from GitHub `main` (`recommend/catalog.refresh_remote`: one API call `git/trees/main?recursive=1` + raw.githubusercontent downloads of changed blobs, cached as `catalog-gh-.yaml`, every 6 h; GUI at start + Settings → Data "Check now"; CLI background thread + `frameport catalog-update`). `catalog.load()` only reads the cache. A remote entry is skipped (`unusable_reason`) when it names a patch/adapter key this app doesn't register, has a field `CatalogEntry` doesn't know, or `min_app` > this version: add `min_app` (or rely on new patch ids) when a config needs an unreleased FramePort. Remote beats bundled unless the bundled entry has a later updated/verified date; the user's own entries still win as before. Off: setting `catalog.auto_update`, env FRAMEPORT_NO_CATALOG_UPDATE; FRAMEPORT_CATALOG_URL still overrides the source. So: **pushing a catalog change to main reaches users within 6 h** (their recipes follow via catalog_rev → Update on Frame). diff --git a/artifacts/SHA256SUMS b/artifacts/SHA256SUMS index ac26c88..e6f4444 100644 --- a/artifacts/SHA256SUMS +++ b/artifacts/SHA256SUMS @@ -1,6 +1,6 @@ 48a3ecad77ec0f3215b29070e994590eb9579e04d3c88a47f37f306f9aeb01d9 ./arm64-v8a/libfp_langpack.so a5b03bb7ae5d953d7ded2bf117b0a19c57fae604f16f54310847f8e89593dde6 ./arm64-v8a/libfp_ovrp.so -6d2b9f8cea2f2cf33b8ff30d29a2f3d9c4d55549260a867168a24e44abada021 ./arm64-v8a/libfp_vk.so +175f19d5373229f29acf71bc7cfefcf9bd4c8fa30fb8fe65689bd7922ce1dbbb ./arm64-v8a/libfp_vk.so 8e00552d1ab90f82749db3caad4f773351fa8a28edcc5ea5cbd1b31c434ff32a ./arm64-v8a/libframe_xrshim.so 3dfcb0f2bec47857bcecc7261c58e076d872d4649718c40850f29e10c75477dd ./arm64-v8a/libglshim.so 3e05bb8561c7fd8787b3120739b3ce66c68badb5b00571bd50ae7e6c1f159274 ./arm64-v8a/libopenxr_loader_generic.so diff --git a/artifacts/arm64-v8a/libfp_vk.so b/artifacts/arm64-v8a/libfp_vk.so index 9ad4e91ea565cb91fcab8627295c8b8bea0c9452..ef66c4dd249b68df730e722263ed32e60ff5da08 100644 GIT binary patch literal 27504 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 QzCze#@%KLcoOk;F1Cr1c_5c6? delta 8608 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 diff --git a/native/vkshim/vkshim.c b/native/vkshim/vkshim.c index f49826a..e7dca5d 100644 --- a/native/vkshim/vkshim.c +++ b/native/vkshim/vkshim.c @@ -13,6 +13,13 @@ // copied with extra words inserted at a byte offset, e.g. stores that initialize locals a shader reads before writing // (an undefined loop counter hung the GPU in VR4's campaign). Every other module passes through unchanged. // Format: vk_shader_fix=:::,,...[;] +// +// Query slots (adapter setting vk_query_slots=2, per game): with multiview, Vulkan counts a query that runs in a +// multiview render pass as one query per view (N consecutive indices). Mesa's drivers (the Frame's Turnip) write a +// zero result into the extra views' slots; Meta's Quest driver doesn't. Engines that allocate one slot per query +// (Into The Radius 2, Unreal 5) then have the next object's occlusion query overwritten with "0 samples" and cull it: +// models pop in and out. With the setting, occlusion pools get N slots per query; query i uses slot N*i, so the extra +// views land in N*i+1.., and results are read from slot N*i only (view 0's count; Turnip counts every view there). #define _GNU_SOURCE #include #include @@ -74,12 +81,18 @@ static void parse_fixes(char *v) { } } +static int query_slots = 1; // vk_query_slots: slots per occlusion query (1 = untouched) + static void read_settings(const char *path) { FILE *f = fopen(path, "r"); if (!f) return; char line[4096]; while (fgets(line, sizeof line, f)) { line[strcspn(line, "\r\n")] = 0; + if (!strncmp(line, "vk_query_slots=", 15)) { + int v = atoi(line + 15); + query_slots = v >= 2 && v <= 4 ? v : 1; + } if (!strncmp(line, "vk_shader_fix=", 14)) { nfixes = 0; // later sources override earlier ones (as for the adapter's settings) parse_fixes(line + 14); @@ -182,6 +195,7 @@ static uint32_t *fixed_shader(const uint32_t *code, size_t size, size_t *out_siz static void init(void) { read_all_settings(); if (nfixes) LOG("vk shim: %d shader fix(es) configured", nfixes); + if (query_slots > 1) LOG("vk shim: %d slots per occlusion query", query_slots); real_vk = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL); if (real_vk) { real_gipa = (PFN_vkGetInstanceProcAddr)dlsym(real_vk, "vkGetInstanceProcAddr"); @@ -356,8 +370,11 @@ static VkRenderPassCreateInfo2 clean_create_info(Arena *a, const VkRenderPassCre // ---------------------------------------------------------------- entry points #define MAX_DEVICES 8 -enum { FN_RP2, FN_RP2KHR, FN_CSM, FN_COUNT }; -static const char *const FN_NAMES[FN_COUNT] = {"vkCreateRenderPass2", "vkCreateRenderPass2KHR", "vkCreateShaderModule"}; +enum { FN_RP2, FN_RP2KHR, FN_CSM, FN_CQP, FN_DQP, FN_BQ, FN_EQ, FN_CRQP, FN_RQP, FN_RQPEXT, FN_GQPR, FN_CCQPR, FN_COUNT }; +static const char *const FN_NAMES[FN_COUNT] = { + "vkCreateRenderPass2", "vkCreateRenderPass2KHR", "vkCreateShaderModule", "vkCreateQueryPool", "vkDestroyQueryPool", + "vkCmdBeginQuery", "vkCmdEndQuery", "vkCmdResetQueryPool", "vkResetQueryPool", "vkResetQueryPoolEXT", + "vkGetQueryPoolResults", "vkCmdCopyQueryPoolResults"}; static struct { VkDevice device; PFN_vkVoidFunction fn[FN_COUNT]; } devices[MAX_DEVICES]; static PFN_vkCreateRenderPass2 fallback_rp2khr; // from vkGetInstanceProcAddr static pthread_mutex_t dev_lock = PTHREAD_MUTEX_INITIALIZER; @@ -428,6 +445,161 @@ EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkCreateShaderModule(VkDevice device, cons return r; } +// ---------------------------------------------------------------- query slots + +// Command buffer functions carry no VkDevice: their next implementation is the one the (single) game device got. +static PFN_vkVoidFunction cmd_fn[FN_COUNT]; +static PFN_vkVoidFunction any_fn(VkDevice device, int which) { + PFN_vkVoidFunction fn = device ? device_fn(device, which) : NULL; + if (!fn) fn = cmd_fn[which]; + if (!fn && real_vk) fn = (PFN_vkVoidFunction)dlsym(real_vk, FN_NAMES[which]); + return fn; +} + +#define MAX_POOLS 4096 +static uint64_t occlusion_pools[MAX_POOLS]; // open addressing; 0 = empty, 1 = deleted +static pthread_mutex_t pool_lock = PTHREAD_MUTEX_INITIALIZER; +static int pools_logged; + +static unsigned pool_hash(uint64_t h) { return (unsigned)((h * 0x9E3779B97F4A7C15ull) >> 52) % MAX_POOLS; } + +static void pool_add(VkQueryPool pool) { + uint64_t h = (uint64_t)pool; + pthread_mutex_lock(&pool_lock); + for (unsigned i = pool_hash(h), n = 0; n < MAX_POOLS; i = (i + 1) % MAX_POOLS, n++) + if (occlusion_pools[i] <= 1) { occlusion_pools[i] = h; break; } + pthread_mutex_unlock(&pool_lock); +} + +static int pool_slot(VkQueryPool pool, int remove) { + uint64_t h = (uint64_t)pool; + if (h <= 1) return 0; + int found = 0; + pthread_mutex_lock(&pool_lock); + for (unsigned i = pool_hash(h), n = 0; n < MAX_POOLS && occlusion_pools[i]; i = (i + 1) % MAX_POOLS, n++) + if (occlusion_pools[i] == h) { + found = 1; + if (remove) occlusion_pools[i] = 1; + break; + } + pthread_mutex_unlock(&pool_lock); + return found; +} + +// the remapped first query of `pool` (unchanged for pools that aren't remapped) +static uint32_t q(VkQueryPool pool, uint32_t query) { return pool_slot(pool, 0) ? query * (uint32_t)query_slots : query; } + +static VKAPI_ATTR VkResult VKAPI_CALL create_query_pool(VkDevice device, const VkQueryPoolCreateInfo *ci, + const VkAllocationCallbacks *alloc, VkQueryPool *pool) { + PFN_vkCreateQueryPool real = (PFN_vkCreateQueryPool)any_fn(device, FN_CQP); + if (!real) return VK_ERROR_INITIALIZATION_FAILED; + if (!ci || ci->queryType != VK_QUERY_TYPE_OCCLUSION || query_slots < 2) return real(device, ci, alloc, pool); + VkQueryPoolCreateInfo copy = *ci; + copy.queryCount = ci->queryCount * (uint32_t)query_slots; + VkResult r = real(device, ©, alloc, pool); + if (r == VK_SUCCESS) { + pool_add(*pool); + if (pools_logged++ < 5) + LOG("vk shim: occlusion query pool %u -> %u slots", ci->queryCount, copy.queryCount); + } + return r; +} + +static VKAPI_ATTR void VKAPI_CALL destroy_query_pool(VkDevice device, VkQueryPool pool, const VkAllocationCallbacks *alloc) { + PFN_vkDestroyQueryPool real = (PFN_vkDestroyQueryPool)any_fn(device, FN_DQP); + pool_slot(pool, 1); + if (real) real(device, pool, alloc); +} + +static VKAPI_ATTR void VKAPI_CALL cmd_begin_query(VkCommandBuffer cb, VkQueryPool pool, uint32_t query, + VkQueryControlFlags flags) { + PFN_vkCmdBeginQuery real = (PFN_vkCmdBeginQuery)any_fn(NULL, FN_BQ); + if (real) real(cb, pool, q(pool, query), flags); +} + +static VKAPI_ATTR void VKAPI_CALL cmd_end_query(VkCommandBuffer cb, VkQueryPool pool, uint32_t query) { + PFN_vkCmdEndQuery real = (PFN_vkCmdEndQuery)any_fn(NULL, FN_EQ); + if (real) real(cb, pool, q(pool, query)); +} + +static uint32_t span(VkQueryPool pool, uint32_t count) { return pool_slot(pool, 0) ? count * (uint32_t)query_slots : count; } + +static VKAPI_ATTR void VKAPI_CALL cmd_reset_query_pool(VkCommandBuffer cb, VkQueryPool pool, uint32_t first, uint32_t count) { + PFN_vkCmdResetQueryPool real = (PFN_vkCmdResetQueryPool)any_fn(NULL, FN_CRQP); + if (real) real(cb, pool, q(pool, first), span(pool, count)); +} + +static void reset_query_pool(VkDevice device, VkQueryPool pool, uint32_t first, uint32_t count, int ext) { + PFN_vkResetQueryPool real = (PFN_vkResetQueryPool)any_fn(device, ext ? FN_RQPEXT : FN_RQP); + if (real) real(device, pool, q(pool, first), span(pool, count)); +} +static VKAPI_ATTR void VKAPI_CALL host_reset_query_pool(VkDevice d, VkQueryPool p, uint32_t f, uint32_t c) { reset_query_pool(d, p, f, c, 0); } +static VKAPI_ATTR void VKAPI_CALL host_reset_query_pool_ext(VkDevice d, VkQueryPool p, uint32_t f, uint32_t c) { reset_query_pool(d, p, f, c, 1); } + +// Results come from slot N*i of each query, one query at a time: the extra slots are only written in multiview passes, +// so reading them (with VK_QUERY_RESULT_WAIT_BIT) could wait forever. +static VKAPI_ATTR VkResult VKAPI_CALL get_query_pool_results(VkDevice device, VkQueryPool pool, uint32_t first, uint32_t count, + size_t size, void *data, VkDeviceSize stride, + VkQueryResultFlags flags) { + PFN_vkGetQueryPoolResults real = (PFN_vkGetQueryPoolResults)any_fn(device, FN_GQPR); + if (!real) return VK_ERROR_INITIALIZATION_FAILED; + if (!pool_slot(pool, 0) || !count) return real(device, pool, first, count, size, data, stride, flags); + VkResult result = VK_SUCCESS; + for (uint32_t i = 0; i < count; i++) { + size_t offset = (size_t)(i * stride); + if (offset >= size) break; + VkResult r = real(device, pool, (first + i) * (uint32_t)query_slots, 1, size - offset, (uint8_t *)data + offset, + stride, flags); + if (r < 0) return r; + if (r == VK_NOT_READY) result = VK_NOT_READY; + } + return result; +} + +static VKAPI_ATTR void VKAPI_CALL cmd_copy_query_pool_results(VkCommandBuffer cb, VkQueryPool pool, uint32_t first, + uint32_t count, VkBuffer buffer, VkDeviceSize offset, + VkDeviceSize stride, VkQueryResultFlags flags) { + PFN_vkCmdCopyQueryPoolResults real = (PFN_vkCmdCopyQueryPoolResults)any_fn(NULL, FN_CCQPR); + if (!real) return; + if (!pool_slot(pool, 0)) { real(cb, pool, first, count, buffer, offset, stride, flags); return; } + for (uint32_t i = 0; i < count; i++) + real(cb, pool, (first + i) * (uint32_t)query_slots, 1, buffer, offset + i * stride, stride, flags); +} + +// The same entry points for engines that dlsym them from the shim instead of asking vkGet*ProcAddr +EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkCreateQueryPool(VkDevice d, const VkQueryPoolCreateInfo *ci, + const VkAllocationCallbacks *a, VkQueryPool *p) { + pthread_once(&once, init); + return create_query_pool(d, ci, a, p); +} +EXPORT VKAPI_ATTR void VKAPI_CALL vkDestroyQueryPool(VkDevice d, VkQueryPool p, const VkAllocationCallbacks *a) { + pthread_once(&once, init); + destroy_query_pool(d, p, a); +} +EXPORT VKAPI_ATTR void VKAPI_CALL vkCmdBeginQuery(VkCommandBuffer cb, VkQueryPool p, uint32_t i, VkQueryControlFlags f) { + pthread_once(&once, init); + cmd_begin_query(cb, p, i, f); +} +EXPORT VKAPI_ATTR void VKAPI_CALL vkCmdEndQuery(VkCommandBuffer cb, VkQueryPool p, uint32_t i) { + pthread_once(&once, init); + cmd_end_query(cb, p, i); +} +EXPORT VKAPI_ATTR void VKAPI_CALL vkCmdResetQueryPool(VkCommandBuffer cb, VkQueryPool p, uint32_t f, uint32_t c) { + pthread_once(&once, init); + cmd_reset_query_pool(cb, p, f, c); +} +EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkGetQueryPoolResults(VkDevice d, VkQueryPool p, uint32_t f, uint32_t c, size_t s, + void *data, VkDeviceSize st, VkQueryResultFlags fl) { + pthread_once(&once, init); + return get_query_pool_results(d, p, f, c, s, data, st, fl); +} +EXPORT VKAPI_ATTR void VKAPI_CALL vkCmdCopyQueryPoolResults(VkCommandBuffer cb, VkQueryPool p, uint32_t f, uint32_t c, + VkBuffer b, VkDeviceSize o, VkDeviceSize st, + VkQueryResultFlags fl) { + pthread_once(&once, init); + cmd_copy_query_pool_results(cb, p, f, c, b, o, st, fl); +} + static int wrapped_index(const char *name) { for (int i = 0; i < FN_COUNT; i++) if (!strcmp(name, FN_NAMES[i])) return i; @@ -439,6 +611,19 @@ static PFN_vkVoidFunction wrap(const char *name) { case FN_RP2: return (PFN_vkVoidFunction)vkCreateRenderPass2; case FN_RP2KHR: return (PFN_vkVoidFunction)create_render_pass2_khr; case FN_CSM: return nfixes ? (PFN_vkVoidFunction)vkCreateShaderModule : NULL; // nothing to fix: no detour + default: break; + } + if (query_slots < 2) return NULL; // no query remapping: no detours + switch (wrapped_index(name)) { + case FN_CQP: return (PFN_vkVoidFunction)create_query_pool; + case FN_DQP: return (PFN_vkVoidFunction)destroy_query_pool; + case FN_BQ: return (PFN_vkVoidFunction)cmd_begin_query; + case FN_EQ: return (PFN_vkVoidFunction)cmd_end_query; + case FN_CRQP: return (PFN_vkVoidFunction)cmd_reset_query_pool; + case FN_RQP: return (PFN_vkVoidFunction)host_reset_query_pool; + case FN_RQPEXT: return (PFN_vkVoidFunction)host_reset_query_pool_ext; + case FN_GQPR: return (PFN_vkVoidFunction)get_query_pool_results; + case FN_CCQPR: return (PFN_vkVoidFunction)cmd_copy_query_pool_results; default: return NULL; } } @@ -460,6 +645,7 @@ EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice dev devices[slot].device = device; devices[slot].fn[which] = fn; } + cmd_fn[which] = fn; pthread_mutex_unlock(&dev_lock); return w; } @@ -473,5 +659,7 @@ EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance PFN_vkVoidFunction w = fn ? wrap(name) : NULL; if (!w) return fn; if (!strcmp(name, "vkCreateRenderPass2KHR")) fallback_rp2khr = (PFN_vkCreateRenderPass2)fn; + int which = wrapped_index(name); + if (which >= FN_CQP && !cmd_fn[which]) cmd_fn[which] = fn; return w; } diff --git a/src/frameport/locales/template.json b/src/frameport/locales/template.json index 22eda54..48862fe 100644 --- a/src/frameport/locales/template.json +++ b/src/frameport/locales/template.json @@ -1111,7 +1111,9 @@ "VrApi → OpenXR adapter (OVRPort)": "", "VrApi → OpenXR bridge": "", "Vulkan shader fixes": "", + "Vulkan shim (frame.vk_sanitize): 2 gives every occlusion query room for both eyes. In multiview passes the Frame's driver writes a zero result for the second eye into the next query, so engines that count on one slot cull visible objects (models popping in and out, e.g. Into The Radius 2). 0 = off.": "", "Vulkan shim (frame.vk_sanitize): SPIR-V modules matching size + SHA-256 get words inserted at a byte offset, e.g. stores that initialize locals a shader reads before writing (an undefined loop counter hung the GPU in VR4's campaign). Format: :::,,...; several separated by ';'. Comes from a game's recipe.": "", + "Vulkan shim: slots per occlusion query": "", "Vulkan: drop invalid render-pass pointers": "", "v{remote} (updates on the next action)": "", "Wait for the game's GPU before showing an image": "", diff --git a/src/frameport/patches/settings.py b/src/frameport/patches/settings.py index 17ded57..b292cc5 100644 --- a/src/frameport/patches/settings.py +++ b/src/frameport/patches/settings.py @@ -119,14 +119,19 @@ SETTINGS = [ "e.g. stores that initialize locals a shader reads before writing (an undefined loop counter hung the GPU in " "VR4's campaign). Format: :::,,...; several separated by ';'. Comes from " "a game's recipe."), + ("vk_query_slots", "int", 0, "Vulkan shim: slots per occlusion query", + "Vulkan shim (frame.vk_sanitize): 2 gives every occlusion query room for both eyes. In multiview passes the " + "Frame's driver writes a zero result for the second eye into the next query, so engines that count on one slot " + "cull visible objects (models popping in and out, e.g. Into The Radius 2). 0 = off."), ("gl_hide_multiview", "int", 1, "GL shim: hide multiview", "GL shim only: hide GL_OVR_multiview so all passes use single-view shaders. For GLES games whose multiview " "shaders fail on single-view render targets (e.g. Path of the Warrior)."), ] -# settings that need a new build, not only new settings files: the Vulkan shim learned vk_shader_fix in 0.6.4 -REVISIONS = {"vk_shader_fix": 2} +# settings that need a new build, not only new settings files: the Vulkan shim learned vk_shader_fix in 0.6.4 and +# vk_query_slots in 0.9.2 +REVISIONS = {"vk_shader_fix": 2, "vk_query_slots": 3} # How the "Game settings" dialog shows each setting to non-technical users: group, level (common settings are always # shown; advanced ones only under "Show advanced settings"), a plain label and one-line help, the control, and the diff --git a/src/frameport/ui/app.py b/src/frameport/ui/app.py index 217bfa3..0a8b5ca 100644 --- a/src/frameport/ui/app.py +++ b/src/frameport/ui/app.py @@ -114,8 +114,8 @@ class FramePortApp: ft.Container(expand=True), self.updater.card, self.activity_card, - self.power_row, self.conn_card, + self.power_row, # under the Frame card (owner's choice) ], spacing=T.S2), width=T.px(236), bgcolor=T.SIDEBAR, padding=T.S4, border=ft.Border(right=ft.BorderSide(1, T.BORDER))) page.add(ft.Row([sidebar, self.body, self.activity.root], expand=True, spacing=0,