Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenXR: Properly skip frame render when the XR runtime is not yet ready #82752

Merged
merged 1 commit into from
Oct 6, 2023

Conversation

decacis
Copy link
Contributor

@decacis decacis commented Oct 3, 2023

This PR essentially recreates the workaround found here: GodotVR/godot_openxr@484b1a8 to recover when swapchains can't be released.

As mentioned, this is a workaround, but in my opinion is a pretty important one because the issue it fixes prevents people from recording videos of the game among other things, and worst of all, it happens seemingly randomly, which can frustrate players.

I just tried my best to adapt the previous code to the new OpenXR API, so please let me know if something could be done a better way!

Closes #82751

@dhoverml
Copy link

dhoverml commented Oct 4, 2023

I think the issue is that XR_FAILED only checks for return codes < 0. xrAcquireSwapchainImage and xrWaitSwapchainImage can both return > 0 (0 is XR_SUCCESS, and can be checked with the XR_UNQUALIFIED_SUCCESS macro). When receiving non-XR_SUCCESS from either of those functions, then the image swapchain should not be released and xrEndFrame should not be called (I'm surprised you aren't seeing errors for this one too, maybe I did something wrong). It is okay to call xrBeginFrame (which will likely return XR_FRAME_DISCARDED in this scenario) again to restart the frame on the next update loop.

Anyway, instead of potentially destroying/creating image swapchains every frame...

  1. if xrAcquireSwapchainImage != XR_SUCCESS, do not xrEndFrame.
  2. If xrWaitSwapchainImage != XR_SUCCESS, do not xrEndFrame. On the next frame, skip xrAcquireSwapchainImage and try xrWaitSwapchainImage again.
  3. Step '2' can repeat for many frames
  4. Once all image swapchains have been acquired and waited, release all image swapchains and xrEndFrame (OpenXRAPI::end_frame() does this).

@BastiaanOlij
Copy link
Contributor

@dhoverml thats really good feedback thanks, I'm going to look into that more closely asap.

I didn't re-apply the workaround we did in Godot 3 because I want this to be fixed properly this time. Just hadn't come high enough on the priority list yet.

I'm hoping Meta is willing to have a closer look to see why this is happening in the first place, but it could indeed be as simple as us not reacting properly on certain return statusses.

@decacis
Copy link
Contributor Author

decacis commented Oct 5, 2023

I did a quick test with the changes suggested by @dhoverml and they seem to work! From my tests at least, only xrWaitSwapchainImage returns a result > 0 that is not success, and it can happen for a few frames, but checking both xrAcquireSwapchainImage and xrWaitSwapchainImage is better of course.

@BastiaanOlij
Copy link
Contributor

@decacis any chance you have time to adjust your PR to fix it using that return value?

@decacis
Copy link
Contributor Author

decacis commented Oct 5, 2023

@BastiaanOlij done! I tested it with both Vulkan Mobile and GL Compatibility.

By the way, I found that the only way to reproduce the issue consistently is to turn off the screen and turn it back on. Recording triggers it very rarely, taking a screenshot has never triggered it for me and neither has switching to passthrough by double tapping (all of this without the new changes, of course)

@BastiaanOlij
Copy link
Contributor

BastiaanOlij commented Oct 5, 2023

@decacis Hmm, this is pretty interesting, we've already called xrBeginFrame here, so if we skip calling xrEndFrame alltogether we get a mismatch between begin frame and end frame.

I think you can simplify the check to:

if (!XR_UNQUALIFIED_SUCCESS(result)) {
	// Make sure end_frame knows we need to submit an empty frame
	frame_state.shouldRender = false;

	if (XR_FAILED(result)) {
		// Unexpected failure, log this!
		print_line("OpenXR: failed to acquire swapchain image [", get_error_string(result), "]");
		return false;
	} else {
		// In this scenario we silently fail, the XR runtime is simply not ready yet to acquire the swapchain.
		return false;
	}
}

This way we do get a closing xrEndFrame but one that doesn't submit buffers.

It might also be safer to add skip_acquire_swapchain to our OpenXRSwapChainInfo struct. While it is unlikely we can acquire the color texture but not the depth texture, it would prevent issues with a mismatch.

@BastiaanOlij BastiaanOlij changed the title OpenXR - Add a recovery step to recreate swapchains OpenXR - Properly slip frame render when the XR runtime is not yet ready to let us acquire the next image from the swapchain Oct 5, 2023
@BastiaanOlij BastiaanOlij changed the title OpenXR - Properly slip frame render when the XR runtime is not yet ready to let us acquire the next image from the swapchain OpenXR - Properly skip frame render when the XR runtime is not yet ready to let us acquire the next image from the swapchain Oct 5, 2023
Applied a couple of checks suggested by @dhoverml for when the
XrResult is not XR_SUCCESS but is also not a failure. Also simplified
checks from @BastiaanOlij feedback.
@decacis
Copy link
Contributor Author

decacis commented Oct 5, 2023

@BastiaanOlij thank you for the feedback. It looks way cleaner!

Copy link
Contributor

@BastiaanOlij BastiaanOlij left a comment

Choose a reason for hiding this comment

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

I think it would be good to wait a day or two before merging to get some feedback from others to see if this indeed resolves the issue. I've asked @Malcolmnixon to test as well as he ran into this issue this week too.

But other than getting confirmation this fixes the issue, this looks really good.

Thanks @decacis !

@dhoverml
Copy link

dhoverml commented Oct 5, 2023

This way we do get a closing xrEndFrame but one that doesn't submit buffers.

It should be okay to call xrBeginFrame without a previous call to xrEndFrame, since that means the previous frame should be discarded (xrBeginFrame should return XR_FRAME_DISCARDED in this case).

@Malcolmnixon
Copy link
Contributor

I just tested this and it fixes the record issue for me.

@akien-mga akien-mga merged commit 7f8c312 into godotengine:master Oct 6, 2023
15 checks passed
@akien-mga
Copy link
Member

Thanks! And congrats for your first merged Godot contribution 🎉

@decacis decacis deleted the openxr_swapchain_error branch October 6, 2023 16:55
@BastiaanOlij BastiaanOlij added the cherrypick:4.1 Considered for cherry-picking into a future 4.1.x release label Oct 7, 2023
@YuriSizov
Copy link
Contributor

Cherry-picked for 4.1.3.

@YuriSizov YuriSizov removed the cherrypick:4.1 Considered for cherry-picking into a future 4.1.x release label Oct 24, 2023
@YuriSizov YuriSizov changed the title OpenXR - Properly skip frame render when the XR runtime is not yet ready to let us acquire the next image from the swapchain OpenXR: Properly skip frame render when the XR runtime is not yet ready Oct 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

OpenXR swapchain error when recording a video on the Meta Quest 2
6 participants