diff --git a/shim/src/xr_runtime.c b/shim/src/xr_runtime.c index 623198c..d083697 100644 --- a/shim/src/xr_runtime.c +++ b/shim/src/xr_runtime.c @@ -398,14 +398,21 @@ static int dump_pending(void) { } /* Drop any in-flight pipelined state and release held images (session teardown). - * Caller must hold g_xrlock and the session must still be valid. */ + * Caller must hold g_xrlock and the session must still be valid. Only WAITED images + * are released; an acquired-but-unwaited one (waitPending/depthWaitPending) can't be, + * so it stays acquired and the next begin_frame (after a session restart) retries its + * wait — the swapchains outlive the session stop. */ static void pipeline_reset(void) { for (int i = 0; i < g_xr.layerCount; i++) { XrLayer *L = &g_xr.layers[i]; XrSwapchainImageReleaseInfo ri = { XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO }; if (L->presentPending) { xrReleaseSwapchainImage(L->swapchain, &ri); L->presentPending = 0; } + if (L->deferColor) { xrReleaseSwapchainImage(L->swapchain, &ri); L->deferColor = 0; } if (L->imageAcquired) { xrReleaseSwapchainImage(L->swapchain, &ri); L->imageAcquired = 0; } + if (L->deferDepth) { xrReleaseSwapchainImage(L->depthSwapchain, &ri); L->deferDepth = 0; } + if (L->depthAcquired) { xrReleaseSwapchainImage(L->depthSwapchain, &ri); L->depthAcquired = 0; } } + g_deferPresent = 0; g_pending.valid = 0; g_pipelineActive = 0; } @@ -862,43 +869,64 @@ ovrpResult xrr_begin_frame(int frameIndex) { for (int i = 0; i < g_xr.layerCount; i++) { XrLayer *L = &g_xr.layers[i]; if (!L->active || L->swapchain == XR_NULL_HANDLE || L->imageAcquired) continue; - XrSwapchainImageAcquireInfo ai = { XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO }; - XrResult ar = xrAcquireSwapchainImage(L->swapchain, &ai, &L->acquiredIndex); - { /* DIAG: index sequence + any acquire failure (e.g. second acquire while - * the pipeline holds an image -> would explain black). */ - static int ac = 0; - if (ac++ < 24) XRRLOG("pipe: acquire layer=%d rc=%d acquiredIndex=%u presentPending=%d imgCount=%u", - i, (int)ar, L->acquiredIndex, L->presentPending, L->imageCount); + /* A previous frame's wait timed out: that image is still acquired (and is the + * oldest un-waited one), so retry the wait on it — a fresh acquire would leak + * it and desync acquiredIndex from the image the wait actually returns. */ + if (!L->waitPending) { + XrSwapchainImageAcquireInfo ai = { XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO }; + XrResult ar = xrAcquireSwapchainImage(L->swapchain, &ai, &L->acquiredIndex); + { /* DIAG: index sequence + any acquire failure (e.g. second acquire while + * the pipeline holds an image -> would explain black). */ + static int ac = 0; + if (ac++ < 24) XRRLOG("pipe: acquire layer=%d rc=%d acquiredIndex=%u presentPending=%d imgCount=%u", + i, (int)ar, L->acquiredIndex, L->presentPending, L->imageCount); + } + if (ar != XR_SUCCESS) + continue; } - if (ar != XR_SUCCESS) - continue; XrSwapchainImageWaitInfo wi = { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO }; wi.timeout = 100000000; /* 100 ms — never block the render thread forever */ XrResult wr = xrWaitSwapchainImage(L->swapchain, &wi); if (wr == XR_TIMEOUT_EXPIRED) { - /* couldn't get the image in time: release the acquire so we stay - * balanced and skip this layer this frame rather than deadlock. */ + /* couldn't get the image in time: it stays ACQUIRED (an un-waited image + * can't be released), the layer is skipped this frame, and the next + * begin_frame retries the wait rather than deadlocking here. */ static int tw = 0; - if (tw++ < 40) XRRLOG("WaitSwapchainImage TIMEOUT layer=%d idx=%u", i, L->acquiredIndex); - XrSwapchainImageReleaseInfo ri = { XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO }; - xrReleaseSwapchainImage(L->swapchain, &ri); + if (tw++ < 40) XRRLOG("WaitSwapchainImage TIMEOUT layer=%d idx=%u (retry=%d)", + i, L->acquiredIndex, L->waitPending); + L->waitPending = 1; L->imageAcquired = 0; continue; } + if (L->waitPending) { + static int wrc = 0; + if (wrc++ < 40) XRRLOG("WaitSwapchainImage recovered layer=%d idx=%u", i, L->acquiredIndex); + } + L->waitPending = 0; L->imageAcquired = 1; /* acquire+wait the paired depth image (lockstep with color) so UE renders * depth into it; released together in end_frame. Depth only runs in the - * synchronous path (the pipeline holds images and doesn't manage depth). */ + * synchronous path (the pipeline holds images and doesn't manage depth). + * Same timeout rule as color: an un-waited depth image stays acquired and + * its wait is retried next frame. */ L->depthAcquired = 0; if (L->depthSwapchain != XR_NULL_HANDLE && !g_pipelineActive) { - XrSwapchainImageAcquireInfo dai = { XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO }; - if (xrAcquireSwapchainImage(L->depthSwapchain, &dai, &L->depthAcquiredIndex) == XR_SUCCESS) { + int haveDepth = L->depthWaitPending; + if (!haveDepth) { + XrSwapchainImageAcquireInfo dai = { XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO }; + haveDepth = (xrAcquireSwapchainImage(L->depthSwapchain, &dai, + &L->depthAcquiredIndex) == XR_SUCCESS); + } + if (haveDepth) { XrSwapchainImageWaitInfo dwi = { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO }; dwi.timeout = 100000000; if (xrWaitSwapchainImage(L->depthSwapchain, &dwi) == XR_TIMEOUT_EXPIRED) { - XrSwapchainImageReleaseInfo dri = { XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO }; - xrReleaseSwapchainImage(L->depthSwapchain, &dri); /* stay balanced */ + static int dtw = 0; + if (dtw++ < 40) XRRLOG("WaitSwapchainImage(depth) TIMEOUT layer=%d idx=%u", + i, L->depthAcquiredIndex); + L->depthWaitPending = 1; /* keep it acquired; retry next frame */ } else { + L->depthWaitPending = 0; L->depthAcquired = 1; } } @@ -939,6 +967,9 @@ static void build_composition(PendingFrame *pf, if (!s || s->LayerId < 0 || s->LayerId >= g_xr.layerCount) continue; XrLayer *L = &g_xr.layers[s->LayerId]; if (!L->active || L->swapchain == XR_NULL_HANDLE) continue; + /* its image wait timed out in begin_frame: nothing was rendered/released for + * it this frame, so skip the layer (retried next frame). */ + if (L->waitPending) continue; /* projection only: drop quads (diag=1, or copy-ring mode which is eye-only) */ if ((diag == 1 || g_copyRingEngaged) && !L->isEyeFov) continue; @@ -999,7 +1030,8 @@ static void build_composition(PendingFrame *pf, /* Chain per-eye depth so the compositor can positionally reproject * (artifact B). Reverse-Z/infinite-far is UE's Quest convention; * params tunable via debug.re4vr.depth_* . Only in normal mode. */ - if (diag == 0 && !g_pipelineActive && L->depthSwapchain != XR_NULL_HANDLE) { + if (diag == 0 && !g_pipelineActive && L->depthSwapchain != XR_NULL_HANDLE + && !L->depthWaitPending) { XrCompositionLayerDepthInfoKHR *d = &pf->pdepth[eye]; *d = (XrCompositionLayerDepthInfoKHR){ XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR }; d->subImage.swapchain = L->depthSwapchain; @@ -1682,6 +1714,8 @@ void xrr_destroy_layer(int layerId) { xrReleaseSwapchainImage(L->swapchain, &ri); L->imageAcquired = 0; } + /* waitPending/depthWaitPending images are acquired but un-waited and can't be + * released; destroying the swapchain with them held is valid, so just drop them. */ if (L->swapchain != XR_NULL_HANDLE) xrDestroySwapchain(L->swapchain); if (L->depthSwapchain != XR_NULL_HANDLE) xrDestroySwapchain(L->depthSwapchain); if (L->shimCount > 0) xrr_vk_free_images(L->shimImages, L->shimMem, L->shimCount); diff --git a/shim/src/xr_runtime.h b/shim/src/xr_runtime.h index 3d8cd12..3362797 100644 --- a/shim/src/xr_runtime.h +++ b/shim/src/xr_runtime.h @@ -28,6 +28,11 @@ typedef struct { uint32_t depthAcquiredIndex; int depthAcquired; /* paired depth image held this frame */ int imageAcquired; /* this frame's render image is held */ + /* xrWaitSwapchainImage timed out: the image is ACQUIRED but not yet WAITED. It + * cannot be released (spec: CALL_ORDER_INVALID), so it stays acquired and the + * next begin_frame retries the wait on it instead of acquiring another. */ + int waitPending; /* color: acquiredIndex awaiting wait */ + int depthWaitPending; /* depth: depthAcquiredIndex awaiting wait */ /* render-ahead pipeline: the image rendered LAST frame is held one extra * frame so its tile-memory flush completes off the critical path. */ int presentPending; /* holding last frame's image to present */