Idle the render thread while the OpenXR session is stopped - #13
Open
jfrolund wants to merge 1 commit into
Open
Conversation
MCOpenXR already tracks session state in isActive -- set false on XR_SESSION_STATE_STOPPING and _IDLE, true on _READY/_VISIBLE/_FOCUSED -- but the render path never reads it. poll() checks only initialized. So when the session stops (headset taken off, app backgrounded) xrWaitFrame returns immediately with XR_ERROR_SESSION_NOT_RUNNING instead of pacing us to the display rate. Nothing bails out, so xrBeginFrame, xrLocateViews and five xrLocateSpace calls all run and all fail, and with nothing left to block on the render thread free-runs. Measured on a Quest 2: roughly 1400 error lines a second, sustained for as long as the headset was left alone, growing latestlog.txt to 118MB in a single session. The allocation churn from formatting those messages drives the heap to its ceiling, and the game is eventually killed with reason=3 (LOW_MEMORY) -- typically hours later while unattended. It also made the headset's own auto-sleep unusable, since sleeping just drove the same path harder. Return early from updatePose() when the session is not running, sleeping briefly so the thread idles instead of spinning, and skip frame submission in OpenXRStereoRenderer#endFrame for the same reason. Rate limit logError() per call site and result as well, so any other failing call cannot flood the log the same way. The guard goes inside updatePose() rather than poll() because poll() uses paired Profiler.popPush/pop, and returning early there unbalances the profiler stack. After the fix the same test produced zero error lines and 263 bytes of log growth across a 40 minute sleep, and the game has since run for over a day with auto-sleep enabled and no LOW_MEMORY kills.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
MCOpenXRalready tracks session state inisActive— setfalseonXR_SESSION_STATE_STOPPINGand_IDLE,trueon_READY/_VISIBLE/_FOCUSED—but the render path never reads it.
poll()checks onlyinitialized.So when the session stops (headset taken off, app backgrounded)
xrWaitFramereturnsimmediately with
XR_ERROR_SESSION_NOT_RUNNINGinstead of pacing us to the displayrate. Nothing bails out, so
xrBeginFrame,xrLocateViewsand fivexrLocateSpacecalls all run and all fail — and with nothing left to block on, the render thread
free-runs:
Impact, measured on a Quest 2
Roughly 1400 error lines per second, sustained for as long as the headset was left
alone —
latestlog.txtreached 118 MB in one session, with 332,382 error lines.The allocation churn from formatting those messages drives the heap to its ceiling, and
the game is eventually killed with
reason=3 (LOW_MEMORY)— typically hours later whileunattended. It also made the headset's own auto-sleep unusable: sleeping stops the
session, which just drove this path harder, so the game appeared to "crash on sleep".
Change
updatePose()when the session is not running, sleeping briefly sothe thread idles instead of spinning.
OpenXRStereoRenderer#endFramefor the same reason — noframe was begun, so releasing images and submitting a layer can only fail.
logError()per call site and result, reporting a suppressed count, so anyother failing call cannot flood the log the same way.
The guard goes inside
updatePose()rather thanpoll()deliberately:poll()usespaired
Profiler.popPush/pop, and returning early there unbalances the profiler stack.Result
Same test after the fix: zero error lines, and 263 bytes of log growth across a
40 minute sleep. The game has since run for over a day with the headset's auto-sleep
re-enabled and no LOW_MEMORY kills.
Notes
Built and verified against
OpenXR-1.21.10. The on-device validation ran on theOpenXR-1.21.5equivalent of these changes (that being the version the currentQuestCraft release ships), with the
updatePoseguard and thelogErrorthrottle inthe deployed build; the
endFrameguard is included here as the matching counterpart.The same edits apply to the 1.21.5 and 1.21.8 branches if you want them backported.
🤖 Generated with Claude Code