mirror of
https://github.com/spoopyghosty0/frameport.git
synced 2026-10-25 09:02:08 +01:00
FrameBridge: a flipped panel the app finishes drawing after xrEndFrame (no new acquire) is flipped again once its content settles (AC Nexus' calibrate-arms screen stayed upside down)
This commit is contained in:
1 parent
9793e6dbae
commit
595d12a038
5 files changed
+91
-3
No files matched your search
@@ -14,11 +14,11 @@ e32f9b3222bfe2e58c193db809ac2cf54086df36fd2ec6651aa2e58308f6b39d ./arm64-v8a/li
|
||||
4a13a33e112c9e4e3c5ff4337ed6e988a9e5b3602f48d654df148ef5fafbf426 ./arm64-v8a/libfpglmv.so
|
||||
36400eae4ba3807bcfcd54463daa217347032b07218792faa448d8cbbd086a0e ./arm64-v8a/libframe_xrshim.so
|
||||
19ba5ed3c4b780f5ae46251f6958f552acfc5f13c8bb39f995579593fa8990c6 ./arm64-v8a/libglshim.so
|
||||
2503e06e3d0c900c908681c4268f9e21eb25d188018ba6833769648dd5ec4b67 ./arm64-v8a/libopenxr_loader_generic.so
|
||||
0e3f187f6421d0d3a7baf62ff34ff522ed3c3f1a486987eb6b8634d2b2d2541f ./arm64-v8a/libopenxr_loader_generic.so
|
||||
1feaeafad467c4cafdf2b018a4d84b0bee200c3f711697b4ce97e66a3ba256ca ./arm64-v8a/libovrplatformcompat.so
|
||||
a4ed80ae45ca90758239f152ea5a2e8c127d491e1e697ea5680db6fd7f72db07 ./arm64-v8a/libvrapi.so
|
||||
a4ed80ae45ca90758239f152ea5a2e8c127d491e1e697ea5680db6fd7f72db07 ./arm64-v8a/libvrapi_foveation.so
|
||||
ba93402b2418344e3ee4631a135560c830ac1383d0a314d670b381eb6f79474e ./armeabi-v7a/libopenxr_loader_generic.so
|
||||
dc775816ff44ac8dbaea933916f5b6aa87b65662cbeeaa07583229fa7646064b ./armeabi-v7a/libopenxr_loader_generic.so
|
||||
1871eae093432d277da4bc751bf5f3269dfcf11f9168260b0c3caadb0dedb19d ./dex/oculusos-stubs.dex
|
||||
b634ab5640e258563c536e658cad87080553df6f34f62269a21d554844e58bfe ./hevc/COPYING.FFmpeg
|
||||
55aae57d292e9536701f2f8a9dbaf1edcfdd766c5748d07a34da2f5ef197f212 ./hevc/libstagefrighthw.so
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -399,7 +399,7 @@ bar) that never advances; the async loader thread sleeps and OBB reads stop (~16
|
||||
|
||||
## Vader's leak solved / AC Nexus flips
|
||||
|
||||
**Vader's leak solved / AC Nexus flips (2026-10-14):** Vader Immortal leaves GL errors pending on its context; SteamVR's GL interop (`openxr_graphics_gl_openvr.cpp`, `xrclient_<pkg>.txt` in Steam's logs: `SXR_GL_CHECK: error:0x502 ... glGenTextures`) takes the stale error as its own failure once, and from then on `CVRCompositorSharedTextures::BeginCommandBuffer` allocates a command buffer every frame (perf page-fault stacks; ~430 renderD128 mappings + 22 MB/s). Adapter `gl_error_clear` (GLES, catalog on for Vader ep1-3) clears them before xrCreateSwapchain/xrBeginFrame/xrEndFrame: flat memory, no SXR_GL_CHECK (headless). The per-game `xrclient_<pkg>.txt` logs are worth reading for runtime errors. The Frame's runtime caps swapchains per session (XR_ERROR_LIMIT_REACHED from ~15; AC Nexus asks for more): flip emulation now turns quads upright in place (scratch VkImage, once per acquire; transfer usage added to single-layer colour swapchains) instead of a shadow swapchain per quad, which ran into the cap (#138). Headless sessions don't draw AC Nexus's panels (adapter `flip_dump` saves before/after images of flipped quads, for headset runs). 2026-10-23: panels that stayed upside down at random were blits that took longer than the 100 ms wait while the GPU was loading; they were counted as failed and flipped again next frame, so both blits ran. A submitted blit now counts as done (log `flip: blit … still running after 100 ms`), and the shared command buffer waits for it before reuse (`vk_cmd_idle`, also fade_quad/cube_standin). Equirect2 layers (type 1000091000, `flip_tag=1 usable=0`) are dropped, not shown upside down.
|
||||
**Vader's leak solved / AC Nexus flips (2026-10-14):** Vader Immortal leaves GL errors pending on its context; SteamVR's GL interop (`openxr_graphics_gl_openvr.cpp`, `xrclient_<pkg>.txt` in Steam's logs: `SXR_GL_CHECK: error:0x502 ... glGenTextures`) takes the stale error as its own failure once, and from then on `CVRCompositorSharedTextures::BeginCommandBuffer` allocates a command buffer every frame (perf page-fault stacks; ~430 renderD128 mappings + 22 MB/s). Adapter `gl_error_clear` (GLES, catalog on for Vader ep1-3) clears them before xrCreateSwapchain/xrBeginFrame/xrEndFrame: flat memory, no SXR_GL_CHECK (headless). The per-game `xrclient_<pkg>.txt` logs are worth reading for runtime errors. The Frame's runtime caps swapchains per session (XR_ERROR_LIMIT_REACHED from ~15; AC Nexus asks for more): flip emulation now turns quads upright in place (scratch VkImage, once per acquire; transfer usage added to single-layer colour swapchains) instead of a shadow swapchain per quad, which ran into the cap (#138). Headless sessions don't draw AC Nexus's panels (adapter `flip_dump` saves before/after images of flipped quads, for headset runs). 2026-10-23: panels that stayed upside down at random were blits that took longer than the 100 ms wait while the GPU was loading; they were counted as failed and flipped again next frame, so both blits ran. A submitted blit now counts as done (log `flip: blit … still running after 100 ms`), and the shared command buffer waits for it before reuse (`vk_cmd_idle`, also fade_quad/cube_standin). Equirect2 layers (type 1000091000, `flip_tag=1 usable=0`) are dropped, not shown upside down. Same day, headset dumps (`flip_dump`, now also a `shown`/`late` image later): the "calibrate arms" panel was finished by the game after xrEndFrame (raw dump half drawn) and never acquired again, so its drawing landed upside down after the flip. Each flipped image is now sampled (16x16 texels) for ~2 s; a settled change without a new acquire flips it again (log `drawn after its flip`). Flips also use the last released image (release tracking). AC Nexus' recipe lost `device.text_input_window`: its Android window took the controllers' input.
|
||||
|
||||
## Unreal's self-restart
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ static struct {
|
||||
VkDeviceMemory scratch_memory;
|
||||
VkFormat scratch_format;
|
||||
uint32_t scratch_w, scratch_h;
|
||||
// image samples for late-drawing checks (flip_vk.c sample_print): 16x16 texels, host-visible
|
||||
VkBuffer print_buf;
|
||||
VkDeviceMemory print_mem;
|
||||
} vk;
|
||||
|
||||
typedef struct {
|
||||
@@ -79,6 +82,8 @@ typedef struct {
|
||||
// submitted again without a new acquire: flipping them again would undo it)
|
||||
struct { XrRect2Di rect; uint32_t layer; } flipped[MAX_IMAGES][4];
|
||||
uint32_t flipped_count[MAX_IMAGES];
|
||||
// late drawing: a sample of the image right after its flip, the last sample, and frames left to watch it
|
||||
uint32_t print[MAX_IMAGES], seen[MAX_IMAGES], watch[MAX_IMAGES];
|
||||
uint32_t alpha_inverted[MAX_IMAGES]; // alpha_blend_vk.c: array layers whose alpha was inverted since the acquire
|
||||
int dumped; // flip_dump saved this swapchain's quad (its dump number)
|
||||
int shown_dumped, frames_since_dump;
|
||||
@@ -116,6 +121,8 @@ static void flip_on_create_session(const XrSessionCreateInfo *info) {
|
||||
vk.scratch = VK_NULL_HANDLE; // belonged to the previous session's device
|
||||
vk.scratch_memory = VK_NULL_HANDLE;
|
||||
vk.scratch_w = vk.scratch_h = 0;
|
||||
vk.print_buf = VK_NULL_HANDLE;
|
||||
vk.print_mem = VK_NULL_HANDLE;
|
||||
LOG("flip: Vulkan session (queue family %u index %u)", vk.family, vk.index);
|
||||
}
|
||||
}
|
||||
@@ -145,6 +152,7 @@ static void flip_on_acquire(XrSwapchain handle, uint32_t index) {
|
||||
if (t) {
|
||||
t->last_acquired = index;
|
||||
if (index < MAX_IMAGES) t->flipped_count[index] = 0; // new content: upside down again
|
||||
if (index < MAX_IMAGES) t->watch[index] = 0;
|
||||
if (index < MAX_IMAGES) t->alpha_inverted[index] = 0;
|
||||
if(t->acquired_count<MAX_IMAGES)t->acquired[t->acquired_count++]=index;
|
||||
if (t->held_count < MAX_IMAGES) t->held[t->held_count++] = index;
|
||||
@@ -307,6 +315,66 @@ static int scratch_fit(VkFormat format, uint32_t w, uint32_t h) {
|
||||
|
||||
// flip_dump=N (diagnostics): the quad's rectangle as the app drew it ("raw") and as submitted after the flip
|
||||
// ("upright"), saved as files/fb_flip_<n>_<raw|upright>.ppm (8-bit RGBA formats only).
|
||||
#define PRINT_GRID 16
|
||||
#define PRINT_FRAMES 180 // how long a flipped image is watched for late drawing (~2 s)
|
||||
|
||||
// A fingerprint of the rectangle: 16x16 texels on a grid, hashed (0 = no sample). Unity sometimes finishes a panel
|
||||
// after xrEndFrame, so its drawing landed after the flip, upside down, and the image was never acquired again (AC
|
||||
// Nexus' "calibrate arms" screen).
|
||||
static int sample_print(VkImage app, uint32_t layer, XrRect2Di rect, int64_t format, uint32_t *out) {
|
||||
if (format != VK_FORMAT_R8G8B8A8_SRGB && format != VK_FORMAT_R8G8B8A8_UNORM && format != VK_FORMAT_B8G8R8A8_SRGB &&
|
||||
format != VK_FORMAT_B8G8R8A8_UNORM) return 0;
|
||||
VkDeviceSize size = PRINT_GRID * PRINT_GRID * 4;
|
||||
if (!vk.print_buf) {
|
||||
VkBufferCreateInfo bi = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, NULL, 0, size, VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VK_SHARING_MODE_EXCLUSIVE, 0, NULL};
|
||||
if (vk.CreateBuffer(vk.device, &bi, NULL, &vk.print_buf) != VK_SUCCESS) { vk.print_buf = VK_NULL_HANDLE; return 0; }
|
||||
VkMemoryRequirements req;
|
||||
vk.GetBufferMemoryRequirements(vk.device, vk.print_buf, &req);
|
||||
uint32_t type = UINT32_MAX;
|
||||
for (uint32_t i = 0; i < vk.memory.memoryTypeCount && type == UINT32_MAX; ++i)
|
||||
if ((req.memoryTypeBits & (1u << i)) && (vk.memory.memoryTypes[i].propertyFlags &
|
||||
(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) ==
|
||||
(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) type = i;
|
||||
VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, req.size, type};
|
||||
if (type == UINT32_MAX || vk.AllocateMemory(vk.device, &ai, NULL, &vk.print_mem) != VK_SUCCESS ||
|
||||
vk.BindBufferMemory(vk.device, vk.print_buf, vk.print_mem, 0) != VK_SUCCESS) {
|
||||
if (vk.print_mem) vk.FreeMemory(vk.device, vk.print_mem, NULL);
|
||||
vk.DestroyBuffer(vk.device, vk.print_buf, NULL);
|
||||
vk.print_buf = VK_NULL_HANDLE;
|
||||
vk.print_mem = VK_NULL_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!vk_cmd_idle()) return 0;
|
||||
VkBufferImageCopy regions[PRINT_GRID * PRINT_GRID];
|
||||
for (int y = 0; y < PRINT_GRID; ++y)
|
||||
for (int x = 0; x < PRINT_GRID; ++x) {
|
||||
int i = y * PRINT_GRID + x;
|
||||
regions[i] = (VkBufferImageCopy){(VkDeviceSize)i * 4, 0, 0, {VK_IMAGE_ASPECT_COLOR_BIT, 0, layer, 1},
|
||||
{rect.offset.x + (int32_t)((2 * x + 1) * (int64_t)rect.extent.width / (2 * PRINT_GRID)),
|
||||
rect.offset.y + (int32_t)((2 * y + 1) * (int64_t)rect.extent.height / (2 * PRINT_GRID)), 0}, {1, 1, 1}};
|
||||
}
|
||||
VkCommandBufferBeginInfo begin = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
|
||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, NULL};
|
||||
vk.ResetCommandBuffer(vk.cmd, 0);
|
||||
vk.BeginCommandBuffer(vk.cmd, &begin);
|
||||
barrier(app, layer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
|
||||
vk.CmdCopyImageToBuffer(vk.cmd, app, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, vk.print_buf, PRINT_GRID * PRINT_GRID, regions);
|
||||
barrier(app, layer, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
|
||||
vk.EndCommandBuffer(vk.cmd);
|
||||
if (vk_cmd_submit(100000000ull) != 1) return 0; // still running: no sample this frame
|
||||
void *mapped = NULL;
|
||||
if (vk.MapMemory(vk.device, vk.print_mem, 0, size, 0, &mapped) != VK_SUCCESS) return 0;
|
||||
uint32_t hash = 2166136261u;
|
||||
for (VkDeviceSize i = 0; i < size; ++i) hash = (hash ^ ((const unsigned char *)mapped)[i]) * 16777619u;
|
||||
vk.UnmapMemory(vk.device, vk.print_mem);
|
||||
*out = hash ? hash : 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int dump_rect(VkImage app, uint32_t layer, XrRect2Di rect, int64_t format, int n, const char *what) {
|
||||
int written = 0;
|
||||
if (!*snap_dir || (format != VK_FORMAT_R8G8B8A8_SRGB && format != VK_FORMAT_R8G8B8A8_UNORM &&
|
||||
@@ -406,6 +474,21 @@ static const XrCompositionLayerQuad *flip_quad(XrSession session, const XrCompos
|
||||
else if (flip_dumps < flip_dump && dump_rect(t->images[index], layer, rect, t->info.format, flip_dumps + 1, "late"))
|
||||
++flip_dumps;
|
||||
}
|
||||
if (t->flipped_count[index] && t->watch[index] && t->flipped[index][0].layer == layer &&
|
||||
!memcmp(&t->flipped[index][0].rect, &rect, sizeof rect)) { // the image's first flipped quad: drawn into late?
|
||||
--t->watch[index];
|
||||
uint32_t now = 0;
|
||||
if (sample_print(t->images[index], layer, rect, t->info.format, &now) && now != t->print[index]) {
|
||||
if (now == t->seen[index]) { // changed since the flip and settled: the app's late drawing, upside down
|
||||
static int late;
|
||||
if (late++ < 10)
|
||||
LOG("flip: quad swapchain=%p (%ux%u) image %u was drawn after its flip, flipping it again",
|
||||
(void *)(uintptr_t)q->subImage.swapchain, t->info.width, t->info.height, index);
|
||||
t->flipped_count[index] = 0;
|
||||
}
|
||||
t->seen[index] = now;
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < t->flipped_count[index]; ++i)
|
||||
if (t->flipped[index][i].layer == layer && !memcmp(&t->flipped[index][i].rect, &rect, sizeof rect)) goto done;
|
||||
if (t->flipped_count[index] >= 4) goto done; // never seen: more than 4 quads on one image
|
||||
@@ -470,6 +553,11 @@ static const XrCompositionLayerQuad *flip_quad(XrSession session, const XrCompos
|
||||
if (dump) dump_rect(app, layer, rect, t->info.format, dump, "upright");
|
||||
t->flipped[index][t->flipped_count[index]].rect = rect;
|
||||
t->flipped[index][t->flipped_count[index]++].layer = layer;
|
||||
if (t->flipped_count[index] == 1 && submitted == 1 &&
|
||||
sample_print(app, layer, rect, t->info.format, &t->print[index])) {
|
||||
t->seen[index] = t->print[index];
|
||||
t->watch[index] = PRINT_FRAMES;
|
||||
}
|
||||
if (layer_debug && t->traced++ < 12) // layer_debug: its first flips, with the app's acquire/release state
|
||||
LOG("flip: swapchain=%p %ux%u image %u (acquired %u, released %d, held %u) rect=%d,%d layer %u", (void *)(uintptr_t)q->subImage.swapchain,
|
||||
t->info.width, t->info.height, index, t->last_acquired, t->released_any ? (int)t->last_released : -1,
|
||||
|
||||
Reference in new issue
Block a user