Skip to content

Commit a30927e

Browse files
authored
Merge pull request o3de#608 from galibzon/OpenXRFixes
This PR improves XrSpaces location calculation. Previously, XrSpaces were being located each frame during OpenXRVkInput::PollActions() which was called before OpenXRVk::Device::BeginInternal(). The problem with that approach is that xrLocateSpace needs a "predicted display time" for the CURRENT frame. But the "predicted display time" is calculated during OpenXRVk::Device::BeginInternal(), this means that all XrPoses for the CURRENT frame were calculated with the "predicted display time" of the PREVIOUS frame. This was causing the rendered frames to look jittery. In this PR, the XrSpaces are now located during OpenXRVk::Device::BeginInternal() this guarantees that when xrLocateSpace is called it is using the correct "predicted display time" for the CURRENT frame. This improves the jitter because the camera View Srg transforms now feed the correct transform value for each frame improving the stability of the rendered views. Another improvement is that the Base Space used in xrLocateSpace is the LOCAL space instead of the hard coded VIEW space. Also APIS were added to change the Base Space for both Visualized spaces and Controller (Joysticks) spaces. Also the engine is now notified via AZ::RPI::XRSpaceNotificationBus::OnXRSpaceLocationsChanged at the right time so the engine can update the ViewSrg for each eye at the correct time. Another improvement is that the AZ::RPI::PoseData reported to the engine is now given according to the O3DE convention: X+ Right, Y+ forward, Z+ Up. Also added equivalent APIs that report the data as AZ::Transform. Fixed bug when the Xr Poses would be corrupted if the Proximity Sensor was enabled. Now the XrSpaces are reset each time a XR_SESSION_STATE_READY event is received (typically because the Proximity Sensor is disabled)
2 parents 0f16aa2 + 26d2d0a commit a30927e

File tree

16 files changed

+366
-118
lines changed

16 files changed

+366
-118
lines changed

Gems/OpenXRVk/Code/Include/OpenXRVk/OpenXRVkDevice.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,5 @@ namespace OpenXRVk
7979
XrCompositionLayerProjection m_xrLayer{ XR_TYPE_COMPOSITION_LAYER_PROJECTION };
8080
AZStd::vector<XrCompositionLayerProjectionView> m_projectionLayerViews;
8181
AZStd::vector<XrView> m_views;
82-
uint32_t m_viewCountOutput = 0;
8382
};
8483
}

Gems/OpenXRVk/Code/Include/OpenXRVk/OpenXRVkInput.h

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
namespace OpenXRVk
1818
{
19+
class Device;
20+
1921
// Class that will help manage XrActionSet/XrAction
2022
class Input final
2123
: public XR::Input
@@ -24,10 +26,25 @@ namespace OpenXRVk
2426
AZ_CLASS_ALLOCATOR(Input, AZ::SystemAllocator);
2527
AZ_RTTI(Input, "{97ADD1FE-27DF-4F36-9F61-683F881F9477}", XR::Input);
2628

29+
static constexpr char LogName[] = "OpenXRVkInput";
30+
2731
static XR::Ptr<Input> Create();
2832

29-
//! Sync all the actions and update controller
30-
//! as well as various tracked space poses
33+
//! Called by the session when the predicted display time has been updated (typically
34+
//! the device updates the predicted display time during BeginFrame).
35+
//! \param[in] device The device that emitted this event.
36+
//! \param[in] predictedTime The predicted display time for the current frame.
37+
//! \param[out] xrViews Vector where each Eye Pose will be stored. Eye poses are always relative to the VIEW space.
38+
//! The VIEW pose typically represents the pose of the Head (The Head is typically centered
39+
//! between both eyes). Subscript 0 is the left eye, while subscript 1 is the right eye.
40+
//! Returns true if the number of Eye Poses matches the size of @xrViews.
41+
bool UpdateXrSpaceLocations(const OpenXRVk::Device& device, XrTime predictedDisplayTime, AZStd::vector<XrView>& xrViews);
42+
43+
//! Sync all the actions and update controller.
44+
//! REMARK: XrPoses are not updated in this function. Instead, poses are updated upon UpdateXrSpaceLocations().
45+
//! Why? Because PollActions() is called on the main thread outside of the BeginFrame()/EndFrame() loop.
46+
//! This means the if Poses are updated during PollActions(), those poses would be using the predicted display time
47+
//! of the previous frame instead of the current frame.
3148
void PollActions();
3249

3350
//! Initialize various actions/actions sets and add support for Oculus touch bindings
@@ -39,20 +56,30 @@ namespace OpenXRVk
3956
//! Attach action sets
4057
AZ::RHI::ResultCode InitializeActionSets(XrSession xrSession) const;
4158

42-
//! Update Controller space information
59+
//! Updates a Controller/Joystick pose.
4360
void LocateControllerSpace(XrTime predictedDisplayTime, XrSpace baseSpace, AZ::u32 handIndex);
4461

45-
//! Update information for a specific tracked space type (i.e visualizedSpaceType)
62+
//! Update pose information for the view.
4663
void LocateVisualizedSpace(XrTime predictedDisplayTime, XrSpace space, XrSpace baseSpace, OpenXRVk::SpaceType visualizedSpaceType);
4764

4865
//! Return Pose data for a controller attached to a hand index
49-
AZ::RHI::ResultCode GetControllerPose(AZ::u32 handIndex, AZ::RPI::PoseData& outPoseData) const;
66+
//! By default the pose data is converted per O3DE convention: Xright, Yfront, Zup.
67+
//! You can read the raw XR Pose data by setting @convertToO3de to false (Not recommended, but useful for debugging).
68+
AZ::RHI::ResultCode GetControllerPose(AZ::u32 handIndex, AZ::RPI::PoseData& outPoseData, bool convertToO3de = true) const;
69+
70+
//! Same as above but returns the pose data as an AZ::Transform. The AZ::Transform also includes the controller scale.
71+
AZ::RHI::ResultCode GetControllerTransform(AZ::u32 handIndex, AZ::Transform& outTransform, bool convertToO3de = true) const;
5072

51-
//! Return scale for a controller attached to a hand index
73+
//! Returns scale for a controller attached to a hand index
5274
float GetControllerScale(AZ::u32 handIndex) const;
5375

54-
//! Return Pose data for a tracked space type (i.e visualizedSpaceType)
55-
AZ::RHI::ResultCode GetVisualizedSpacePose(OpenXRVk::SpaceType visualizedSpaceType, AZ::RPI::PoseData& outPoseData) const;
76+
//! Return Pose data for a tracked space type (i.e visualizedSpaceType).
77+
//! By default the pose data is converted per O3DE convention: Xright, Yfront, Zup.
78+
//! You can read the raw XR Pose data by setting @convertToO3de to false (Not recommended, but useful for debugging).
79+
AZ::RHI::ResultCode GetVisualizedSpacePose(OpenXRVk::SpaceType visualizedSpaceType, AZ::RPI::PoseData& outPoseData, bool convertToO3de = true) const;
80+
81+
//! Same as above but returns the pose data as an AZ::Transform
82+
AZ::RHI::ResultCode GetVisualizedSpaceTransform(OpenXRVk::SpaceType visualizedSpaceType, AZ::Transform& outTransform, bool convertToO3de = true) const;
5683

5784
//! Get the Squeeze action
5885
XrAction GetSqueezeAction(AZ::u32 handIndex) const;
@@ -107,6 +134,9 @@ namespace OpenXRVk
107134
//! Destroy native objects
108135
void ShutdownInternal() override;
109136

137+
//! Returns true if the number of Eye Poses matches the size of @xrViews.
138+
bool LocateEyeViews(XrTime predictedDisplayTime, AZStd::vector<XrView>& xrViews);
139+
110140
XrActionSet m_actionSet{ XR_NULL_HANDLE };
111141

112142
XrAction m_hapticAction{};

Gems/OpenXRVk/Code/Include/OpenXRVk/OpenXRVkSession.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@ namespace OpenXRVk
4040
//! Return the Xrspace related to the SpaceType enum
4141
XrSpace GetXrSpace(SpaceType spaceType) const;
4242

43+
////////////////////////////////////////////////////////////////////////////////////////////
44+
//! Called by a Device when the predicted display time has been updated (typically
45+
//! the device updates the predicted display time during BeginFrame).
46+
//! See OpenXRVkInput.h UpdateXrSpaceLocations(...) for more details.
47+
void UpdateXrSpaceLocations(const OpenXRVk::Device& device, XrTime predictedDisplayTime, AZStd::vector<XrView>& xrViews);
48+
49+
//! Setters and Getters for the base spaces that will be used
50+
//! when calling xrLocateSpace().
51+
//! By default, the base space for visualization is SpaceType::Local
52+
//! and the base space for Joysticks/controllers is SpaceType::View (aka the Head)
53+
void SetBaseSpaceTypeForVisualization(SpaceType spaceType);
54+
void SetBaseSpaceTypeForControllers(SpaceType spaceType);
55+
SpaceType GetBaseSpaceTypeForVisualization() const;
56+
SpaceType GetBaseSpaceTypeForControllers() const;
57+
4358
//////////////////////////////////////////////////////////////////////////
4459
// XR::Session overrides
4560
AZ::RHI::ResultCode InitInternal(AZ::RHI::XRSessionDescriptor* descriptor) override;
@@ -50,6 +65,7 @@ namespace OpenXRVk
5065
void PollEvents() override;
5166
void LocateControllerSpace(AZ::u32 handIndex) override;
5267
AZ::RHI::ResultCode GetControllerPose(AZ::u32 handIndex, AZ::RPI::PoseData& outPoseData) const override;
68+
AZ::RHI::ResultCode GetControllerTransform(AZ::u32 handIndex, AZ::Transform& outTransform) const override;
5369
AZ::RHI::ResultCode GetControllerStagePose(AZ::u32 handIndex, AZ::RPI::PoseData& outPoseData) const override;
5470
AZ::RHI::ResultCode GetViewFrontPose(AZ::RPI::PoseData& outPoseData) const override;
5571
AZ::RHI::ResultCode GetViewLocalPose(AZ::RPI::PoseData& outPoseData) const override;
@@ -68,12 +84,25 @@ namespace OpenXRVk
6884
void ShutdownInternal() override;
6985
void LogActionSourceName(XrAction action, const AZStd::string_view actionName) const;
7086
Input* GetNativeInput() const;
87+
// Spaces are reset every time the proximity sensor turns off, or the user wears the headset
88+
// when the proximity sensor is ON.
89+
void ResetSpaces();
7190

7291
XrSession m_session = XR_NULL_HANDLE;
7392
XrSessionState m_sessionState = XR_SESSION_STATE_UNKNOWN;
7493
XrEventDataBuffer m_eventDataBuffer;
7594
XrInstance m_xrInstance = XR_NULL_HANDLE;
7695
XrGraphicsBindingVulkan2KHR m_graphicsBinding{ XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR };
96+
97+
// Application defined base space that will used to calculate
98+
// the relative pose of all other spaces.
99+
// Typically SpaceType::Local or SpaceType::Stage.
100+
SpaceType m_baseSpaceTypeForVisualization = SpaceType::Local;
101+
102+
// Application defined base space that will use to calculate
103+
// the relative pose of the joysticks (aka XR Controllers).
104+
// Typically SpaceType::View, but could be SpaceType::Local or SpaceType::Stage.
105+
SpaceType m_baseSpaceTypeForControllers = SpaceType::View;
77106

78107
bool m_sessionRunning = false;
79108
bool m_exitRenderLoop = false;

Gems/OpenXRVk/Code/Include/OpenXRVk/OpenXRVkUtils.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88

99
#pragma once
1010

11+
#include <AzCore/Math/Transform.h>
1112
#include <OpenXRVk_Platform.h>
1213
#include <XR/XRBase.h>
1314

15+
1416
// Macro to generate stringify functions for OpenXR enumerations based data provided in openxr_reflection.h
1517
#define ENUM_CASE_STR(name, val) case name: return #name;
1618
#define MAKE_XR_TO_STRING_FUNC(enumType) \
@@ -63,4 +65,10 @@ namespace OpenXRVk
6365
//! Iterate through the characters while caching the starting pointer to a string
6466
//! and every time ' ' is encountered replace it with '\0' to indicate the end of a string.
6567
AZStd::vector<const char*> ParseExtensionString(char* names);
68+
69+
AZ::Quaternion AzQuaternionFromXrPose(const XrPosef& pose, bool convertCoordinates = true);
70+
AZ::Vector3 AzPositionFromXrPose(const XrPosef& pose, bool convertCoordinates = true);
71+
AZ::Transform AzTransformFromXrPose(const XrPosef& pose, bool convertCoordinates = true);
72+
XrPosef XrPoseFromAzTransform(const AZ::Transform& tm, bool convertCoordinates = true);
73+
6674
}

Gems/OpenXRVk/Code/Source/OpenXRVkDevice.cpp

Lines changed: 18 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ namespace OpenXRVk
5252
{
5353
WARN_IF_UNSUCCESSFUL(result);
5454
}
55+
56+
// Notify the input system that we have a new predicted display time.
57+
// The new predicted display time will be used to calculate XrPoses for the current frame.
58+
session->UpdateXrSpaceLocations(*this, m_frameState.predictedDisplayTime, m_views);
59+
5560
//Always return true as we want EndFrame to always be called.
5661
return true;
5762
}
@@ -112,36 +117,10 @@ namespace OpenXRVk
112117
{
113118
XR::SwapChain::View* baseSwapChainView = baseSwapChain->GetView(viewIndex);
114119
SwapChain::View* swapChainView = static_cast<SwapChain::View*>(baseSwapChainView);
115-
Space* xrSpace = static_cast<Space*>(GetSession()->GetSpace());
116-
Instance* instance = static_cast<Instance*>(GetDescriptor().m_instance.get());
117-
Session* session = static_cast<Session*>(GetSession().get());
118-
XrSession xrSession = session->GetXrSession();
119120
XrSwapchain swapChainHandle = swapChainView->GetSwapChainHandle();
120121

121-
XrViewState viewState{ XR_TYPE_VIEW_STATE };
122-
uint32_t viewCapacityInput = aznumeric_cast<uint32_t>(m_views.size());
123-
124-
XrViewLocateInfo viewLocateInfo{ XR_TYPE_VIEW_LOCATE_INFO };
125-
viewLocateInfo.viewConfigurationType = instance->GetViewConfigType();
126-
viewLocateInfo.displayTime = m_frameState.predictedDisplayTime;
127-
viewLocateInfo.space = xrSpace->GetXrSpace(OpenXRVk::SpaceType::View);
128-
129-
XrResult result = xrLocateViews(xrSession, &viewLocateInfo, &viewState, viewCapacityInput, &m_viewCountOutput, m_views.data());
130-
ASSERT_IF_UNSUCCESSFUL(result);
131-
132-
if ((viewState.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) == 0 ||
133-
(viewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) == 0)
134-
{
135-
//There is no valid tracking poses for the views
136-
return false;
137-
}
138-
139-
AZ_Assert(m_viewCountOutput == viewCapacityInput, "Size mismatch between xrLocateViews %i and xrEnumerateViewConfigurationViews %i", m_viewCountOutput, viewCapacityInput);
140-
AZ_Assert(m_viewCountOutput == static_cast<SwapChain*>(baseSwapChain)->GetViewConfigs().size(), "Size mismatch between xrLocateViews %i and xrEnumerateViewConfigurationViews %i", m_viewCountOutput, static_cast<SwapChain*>(baseSwapChain)->GetViewConfigs().size());
141-
142-
m_projectionLayerViews.resize(m_viewCountOutput);
143122
XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
144-
result = xrAcquireSwapchainImage(swapChainHandle, &acquireInfo, &baseSwapChainView->m_activeImageIndex);
123+
auto result = xrAcquireSwapchainImage(swapChainHandle, &acquireInfo, &baseSwapChainView->m_activeImageIndex);
145124
baseSwapChainView->m_isImageAcquired = (result == XR_SUCCESS);
146125
WARN_IF_UNSUCCESSFUL(result);
147126

@@ -150,6 +129,9 @@ namespace OpenXRVk
150129
result = xrWaitSwapchainImage(swapChainHandle, &waitInfo);
151130
ASSERT_IF_UNSUCCESSFUL(result);
152131

132+
// REMARK: The data in m_views was updated during BeginFrameInternal(), which
133+
// calls session->UpdateXrSpaceLocations(...).
134+
m_projectionLayerViews.resize(m_views.size());
153135
m_projectionLayerViews[viewIndex] = { XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW };
154136
m_projectionLayerViews[viewIndex].pose = m_views[viewIndex].pose;
155137
m_projectionLayerViews[viewIndex].fov = m_views[viewIndex].fov;
@@ -189,30 +171,23 @@ namespace OpenXRVk
189171

190172
AZ::RHI::ResultCode Device::GetViewFov(AZ::u32 viewIndex, AZ::RPI::FovData& outFovData) const
191173
{
192-
if(viewIndex < m_projectionLayerViews.size())
174+
if(viewIndex < m_views.size())
193175
{
194-
outFovData.m_angleLeft = m_projectionLayerViews[viewIndex].fov.angleLeft;
195-
outFovData.m_angleRight = m_projectionLayerViews[viewIndex].fov.angleRight;
196-
outFovData.m_angleUp = m_projectionLayerViews[viewIndex].fov.angleUp;
197-
outFovData.m_angleDown = m_projectionLayerViews[viewIndex].fov.angleDown;
176+
outFovData.m_angleLeft = m_views[viewIndex].fov.angleLeft;
177+
outFovData.m_angleRight = m_views[viewIndex].fov.angleRight;
178+
outFovData.m_angleUp = m_views[viewIndex].fov.angleUp;
179+
outFovData.m_angleDown = m_views[viewIndex].fov.angleDown;
198180
return AZ::RHI::ResultCode::Success;
199181
}
200182
return AZ::RHI::ResultCode::Fail;
201183
}
202184

203185
AZ::RHI::ResultCode Device::GetViewPose(AZ::u32 viewIndex, AZ::RPI::PoseData& outPoseData) const
204186
{
205-
if (viewIndex < m_projectionLayerViews.size())
187+
if (viewIndex < m_views.size())
206188
{
207-
const XrQuaternionf& orientation = m_projectionLayerViews[viewIndex].pose.orientation;
208-
const XrVector3f& position = m_projectionLayerViews[viewIndex].pose.position;
209-
outPoseData.m_orientation.Set(orientation.x,
210-
orientation.y,
211-
orientation.z,
212-
orientation.w);
213-
outPoseData.m_position.Set(position.x,
214-
position.y,
215-
position.z);
189+
outPoseData.m_orientation = AzQuaternionFromXrPose(m_views[viewIndex].pose);
190+
outPoseData.m_position = AzPositionFromXrPose(m_views[viewIndex].pose);
216191
return AZ::RHI::ResultCode::Success;
217192
}
218193
return AZ::RHI::ResultCode::Fail;
@@ -231,4 +206,5 @@ namespace OpenXRVk
231206
m_xrVkDevice = VK_NULL_HANDLE;
232207
m_xrVkPhysicalDevice = VK_NULL_HANDLE;
233208
}
209+
234210
}

0 commit comments

Comments
 (0)