Skip to content

Keep Quest decoded images alive across frame gaps - #42

Merged
demonixis merged 2 commits into
demonixis:developfrom
orangeToT:fix/quest-decoded-image-lifetime
Oct 4, 2026
Merged

demonixis merged 2 commits into
demonixis:developfrom
orangeToT:fix/quest-decoded-image-lifetime

Conversation

@orangeToT

Copy link
Copy Markdown

Problem and change

When AcquireFrame() finds no new image, it has already deleted currentImage_. RenderFrame() then reuses the previous external texture even though its ImageReader slot has been returned to the decoder. An EGL texture reference does not keep that slot acquired. See the NDK image ownership contract.

Acquire into a temporary image and validate its hardware buffer before replacing the held image. On replacement, the render-thread callback completes prior GPU reads with glFinish() before returning the old image. Empty acquisition and hardware-buffer errors preserve the held image; the output counter is consumed only on a successful replacement. The existing three image slots leave the two-slot margin needed by acquireLatestImage().

Related work

#22 also retains the displayed image when no queued frame is due. This patch addresses the current latest-image path, hardware-buffer acquisition failure, and the ordering of GPU completion and image release. Both PRs touch AcquireFrame(); their signature and ownership changes will need reconciliation if both are merged.

Validation

  • PASS: Android assembleDebug assembleRelease, arm64-v8a, Java 17, NDK 28.2.13676358, CMake 3.22.1.
  • PASS: four lifecycle scenarios using the extracted AcquireFrame() body: repeated empty acquisition, invalid hardware buffer, first acquisition, and successful replacement. Unmodified develop: 7 failed checks out of 120; this change: 120/120 passed.
  • NOT_RUN: Quest streaming and GPU/frame-time qualification. Before merge, exercise repeated short video gaps and replacements under GPU load; confirm image stability and measure the cost of the glFinish() wait. The fake completion callback checks call ordering, not real GPU completion.

Reproduce the isolated checks

The standalone reproducer is kept on a separate fork branch so these small fixes do not introduce an Android mocking framework into the runtime suite. It compiles the actual affected function/branch bodies with fake Android APIs and a controlled clock under ASan/UBSan. It does not execute MediaCodec, GLES, or an OpenXR headset.

After downloading the script outside the checkout, run from this PR branch:

python3 /path/to/quest_client_repro.py image . --revision f72f2b20dd0c9e5a4c0b43ca7f9b0f65704aa1e6
# Expected: exit 1, with the regression failures described above.
python3 /path/to/quest_client_repro.py image .
# Expected: exit 0.

Base checked: develop at f72f2b20dd0c9e5a4c0b43ca7f9b0f65704aa1e6. The affected Android source files are identical on unstable at a93418fea5f523107057c1903c6fe753d89da839.

Copilot AI balanced review requested due to automatic review settings October 3, 2026 09:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@demonixis
demonixis deleted the branch demonixis:develop October 3, 2026 20:13
@demonixis demonixis closed this Oct 3, 2026
@demonixis demonixis reopened this Oct 3, 2026
@demonixis

Copy link
Copy Markdown
Owner

Thanks for this PR. Please resolve the conflict and I'll merge it

@demonixis
demonixis merged commit 29bf321 into demonixis:develop Oct 4, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants