From 2f34081d138d5ccdcd422484fd7eadaa92fa5160 Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 4 May 2025 02:22:30 +1000 Subject: [PATCH 1/5] OpenXR cleanup 2, electric boogaloo --- interface/resources/controllers/openxr.json | 9 +- interface/resources/controllers/standard.json | 8 +- plugins/openvr/src/OpenVrHelpers.cpp | 3 + plugins/openxr/CMakeLists.txt | 2 +- plugins/openxr/src/OpenXrDisplayPlugin.cpp | 168 ++++++++---------- plugins/openxr/src/OpenXrDisplayPlugin.h | 5 - plugins/openxr/src/OpenXrInputPlugin.cpp | 11 ++ ...oggleAdvancedMovementForHandControllers.js | 18 +- 8 files changed, 107 insertions(+), 117 deletions(-) diff --git a/interface/resources/controllers/openxr.json b/interface/resources/controllers/openxr.json index a72ffcbdae..5d8b6d089c 100644 --- a/interface/resources/controllers/openxr.json +++ b/interface/resources/controllers/openxr.json @@ -11,13 +11,10 @@ { "from": "OpenXR.LeftGrip", "to": "Standard.LeftGrip", "filters": [{ "type": "deadZone", "min": 0.05 }] }, { "from": "OpenXR.RightGrip", "to": "Standard.RightGrip", "filters": [{ "type": "deadZone", "min": 0.05 }] }, - { "from": "OpenXR.LX", "to": "Standard.LX", "filters": [{ "type": "deadZone", "min": 0.05 }] }, - { "from": "OpenXR.LY", "to": "Standard.LY", "filters": [{ "type": "deadZone", "min": 0.05 }] }, + { "from": "OpenXR.LX", "to": "Standard.LX" }, + { "from": "OpenXR.LY", "to": "Standard.LY" }, - { "from": "OpenXR.LX", "to": "Actions.TranslateX", "peek": true, "filters": [{ "type": "deadZone", "min": 0.05 }] }, - { "from": "OpenXR.LY", "to": "Actions.TranslateZ", "peek": true, "filters": [{ "type": "deadZone", "min": 0.05 }] }, - - { "from": "OpenXR.RX", "to": "Standard.RX"}, + { "from": "OpenXR.RX", "to": "Standard.RX" }, { "from": "OpenXR.RY", "to": "Standard.RY" }, { "from": "OpenXR.LS", "to": "Standard.LS" }, diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index 4f31451de3..8f798c9051 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -2,7 +2,7 @@ "name": "Standard to Action", "channels": [ { "from": "Standard.LY", - "when": ["Application.RightHandDominant", "!Standard.RY"], + "when": ["Application.RightHandDominant"], "to": "Actions.TranslateZ" }, @@ -14,7 +14,7 @@ { "from": "Standard.LX", "when": [ "Application.InHMD", "!Application.AdvancedMovement", "Application.RightHandDominant", - "Application.SnapTurn", "!Standard.RX" + "Application.SnapTurn" ], "to": "Actions.StepYaw", "filters": @@ -58,7 +58,7 @@ }, { "from": "Standard.RY", - "when": ["Application.LeftHandDominant", "!Standard.LY"], + "when": ["Application.LeftHandDominant"], "to": "Actions.TranslateZ" }, @@ -70,7 +70,7 @@ { "from": "Standard.RX", "when": [ "Application.InHMD", "!Application.AdvancedMovement", "Application.LeftHandDominant", - "Application.SnapTurn", "!Standard.RX" + "Application.SnapTurn" ], "to": "Actions.StepYaw", "filters": diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index d5cb7d6c8f..5f2eedeea3 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -304,6 +304,8 @@ void handleOpenVrEvents() { } } +// GetEventTypeNameFromEnum is unimplemented in OpenComposite and xrizer +#if 0 #if DEV_BUILD //qDebug() << "OpenVR: Event " << activeHmd->GetEventTypeNameFromEnum((vr::EVREventType)event.eventType) << "(" << event.eventType << ")"; // FIXME: Reinstate the line above and remove the following lines once the problem with excessive occurrences of @@ -317,6 +319,7 @@ void handleOpenVrEvents() { qDebug() << "OpenVR: Event " << activeHmd->GetEventTypeNameFromEnum((vr::EVREventType)event.eventType) << "(" << event.eventType << ")"; #endif #endif +#endif } } diff --git a/plugins/openxr/CMakeLists.txt b/plugins/openxr/CMakeLists.txt index 847a41880e..30d8282f02 100644 --- a/plugins/openxr/CMakeLists.txt +++ b/plugins/openxr/CMakeLists.txt @@ -28,4 +28,4 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") target_compile_definitions(openxr PRIVATE -DDONT_REDEFINE_LERP) endif() -set_property(TARGET openxr PROPERTY CXX_STANDARD 20) \ No newline at end of file +set_property(TARGET openxr PROPERTY CXX_STANDARD 20) diff --git a/plugins/openxr/src/OpenXrDisplayPlugin.cpp b/plugins/openxr/src/OpenXrDisplayPlugin.cpp index 0137ac22fd..927d5f6668 100644 --- a/plugins/openxr/src/OpenXrDisplayPlugin.cpp +++ b/plugins/openxr/src/OpenXrDisplayPlugin.cpp @@ -30,9 +30,6 @@ constexpr GLint XR_PREFERRED_COLOR_FORMAT = GL_SRGB8_ALPHA8; OpenXrDisplayPlugin::OpenXrDisplayPlugin(std::shared_ptr c) { _context = c; - _presentOnlyOnce = true; - _lastFrameTime = 1.0f / 90.0f; - _estimatedTargetFramerate = 90.0f; } bool OpenXrDisplayPlugin::isSupported() const { @@ -88,7 +85,8 @@ glm::mat4 OpenXrDisplayPlugin::getCullingProjection(const glm::mat4& baseProject // but it does do vsync on its own, // so just push out frames as vsync allows float OpenXrDisplayPlugin::getTargetFrameRate() const { - return std::numeric_limits::max(); + // predictedDisplayPeriod is delta nanoseconds, so convert it to frames per second + return std::min(1.0f, 1.0f / (_lastFrameState.predictedDisplayPeriod / 1e9f)); } bool OpenXrDisplayPlugin::initViews() { @@ -260,15 +258,6 @@ void OpenXrDisplayPlugin::init() { emit deviceConnected(getName()); } -// FIXME: For some reason, OpenVR and OVR don't need this, -// and the game tick counter works as expected. In XR, it -// doesn't behave properly, so we have to emulate vsync delay manually. -void OpenXrDisplayPlugin::idle() { - float remainingUntilFrame = std::max(0.0f, _lastFrameTime - (1.0f / _estimatedTargetFramerate)); - std::chrono::duration> duration(remainingUntilFrame); - std::this_thread::sleep_for(duration); -} - const QString OpenXrDisplayPlugin::getName() const { return QString("OpenXR: %1").arg(_context->_systemName); } @@ -337,8 +326,6 @@ void OpenXrDisplayPlugin::resetSensors() { } bool OpenXrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { - std::chrono::time_point measureStart = std::chrono::high_resolution_clock::now(); - _context->pollEvents(); if (_context->_shouldQuit) { @@ -347,7 +334,7 @@ bool OpenXrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } if (!_context->_shouldRunFrameCycle) { - qCWarning(xr_display_cat, "beginFrameRender: Shoudln't run frame cycle. Skipping renderin frame %d", frameIndex); + qCWarning(xr_display_cat, "beginFrameRender: Shouldn't run frame cycle. Skipping renderin frame %d", frameIndex); return true; } @@ -367,83 +354,8 @@ bool OpenXrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } } - _lastFrameState = { .type = XR_TYPE_FRAME_STATE }; - XrResult result = xrWaitFrame(_context->_session, nullptr, &_lastFrameState); - - if (!xrCheck(_context->_instance, result, "xrWaitFrame failed")) - return false; - - if (!_context->beginFrame()) - return false; - - _context->_lastPredictedDisplayTime = _lastFrameState.predictedDisplayTime; - - std::vector eye_views(_viewCount); - for (uint32_t i = 0; i < _viewCount; i++) { - eye_views[i].type = XR_TYPE_VIEW; - } - - // TODO: Probably shouldn't call xrLocateViews twice. Use only view space views? - XrViewLocateInfo eyeViewLocateInfo = { - .type = XR_TYPE_VIEW_LOCATE_INFO, - .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, - .displayTime = _lastFrameState.predictedDisplayTime, - .space = _context->_viewSpace, - }; - - XrViewState eyeViewState = { .type = XR_TYPE_VIEW_STATE }; - - result = xrLocateViews(_context->_session, &eyeViewLocateInfo, &eyeViewState, _viewCount, &_viewCount, eye_views.data()); - if (!xrCheck(_context->_instance, result, "Could not locate views")) - return false; - - for (uint32_t i = 0; i < 2; i++) { - vec3 eyePosition = xrVecToGlm(eye_views[i].pose.position); - quat eyeOrientation = xrQuatToGlm(eye_views[i].pose.orientation); - _eyeOffsets[i] = controller::Pose(eyePosition, eyeOrientation).getMatrix(); - } - - _lastViewState = { .type = XR_TYPE_VIEW_STATE }; - - XrViewLocateInfo viewLocateInfo = { - .type = XR_TYPE_VIEW_LOCATE_INFO, - .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, - .displayTime = _lastFrameState.predictedDisplayTime, - .space = _context->_stageSpace, - }; - - result = xrLocateViews(_context->_session, &viewLocateInfo, &_lastViewState, _viewCount, &_viewCount, _views.value().data()); - if (!xrCheck(_context->_instance, result, "Could not locate views")) - return false; - - for (uint32_t i = 0; i < _viewCount; i++) { - _projectionLayerViews[i].pose = _views.value()[i].pose; - _projectionLayerViews[i].fov = _views.value()[i].fov; - } - - XrSpaceLocation headLocation = { - .type = XR_TYPE_SPACE_LOCATION, - .pose = XR_INDENTITY_POSE, - }; - xrLocateSpace(_context->_viewSpace, _context->_stageSpace, _lastFrameState.predictedDisplayTime, &headLocation); - - glm::vec3 headPosition = xrVecToGlm(headLocation.pose.position); - glm::quat headOrientation = xrQuatToGlm(headLocation.pose.orientation); - _context->_lastHeadPose = controller::Pose(headPosition, headOrientation); - - _currentRenderFrameInfo = FrameInfo(); - _currentRenderFrameInfo.renderPose = _context->_lastHeadPose.getMatrix(); - _currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; _frameInfos[frameIndex] = _currentRenderFrameInfo; - std::chrono::time_point measureEnd = std::chrono::high_resolution_clock::now(); - std::chrono::duration> delta = measureEnd - measureStart; - _lastFrameTime = delta.count(); - auto newEstimatedFramerate =(1.0f / _lastFrameTime); - if (_estimatedTargetFramerate < newEstimatedFramerate) { - _estimatedTargetFramerate = newEstimatedFramerate; - } - return HmdDisplayPlugin::beginFrameRender(frameIndex); } @@ -468,11 +380,22 @@ void OpenXrDisplayPlugin::compositeLayers() { void OpenXrDisplayPlugin::hmdPresent() { if (!_context->_shouldRunFrameCycle) { - qCWarning(xr_display_cat, "hmdPresent: Shoudln't run frame cycle. Skipping renderin frame %d", + qCWarning(xr_display_cat, "hmdPresent: Shouldn't run frame cycle. Skipping renderin frame %d", _currentFrame->frameIndex); return; } + _lastFrameState = { .type = XR_TYPE_FRAME_STATE }; + XrResult result = xrWaitFrame(_context->_session, nullptr, &_lastFrameState); + + if (!xrCheck(_context->_instance, result, "xrWaitFrame failed")) + return; + + if (!_context->beginFrame()) + return; + + updatePresentPose(); + if (_lastFrameState.shouldRender) { // TODO: Use multiview swapchain for (uint32_t i = 0; i < 2; i++) { @@ -561,6 +484,67 @@ bool OpenXrDisplayPlugin::isHmdMounted() const { } void OpenXrDisplayPlugin::updatePresentPose() { + _context->_lastPredictedDisplayTime = _lastFrameState.predictedDisplayTime; + + std::vector eye_views(_viewCount); + for (uint32_t i = 0; i < _viewCount; i++) { + eye_views[i].type = XR_TYPE_VIEW; + } + + // TODO: Probably shouldn't call xrLocateViews twice. Use only view space views? + XrViewLocateInfo eyeViewLocateInfo = { + .type = XR_TYPE_VIEW_LOCATE_INFO, + .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + .displayTime = _lastFrameState.predictedDisplayTime, + .space = _context->_viewSpace, + }; + + XrViewState eyeViewState = { .type = XR_TYPE_VIEW_STATE }; + + XrResult result = xrLocateViews(_context->_session, &eyeViewLocateInfo, &eyeViewState, _viewCount, &_viewCount, eye_views.data()); + if (!xrCheck(_context->_instance, result, "Could not locate views")) + return; + + for (uint32_t i = 0; i < 2; i++) { + vec3 eyePosition = xrVecToGlm(eye_views[i].pose.position); + quat eyeOrientation = xrQuatToGlm(eye_views[i].pose.orientation); + _eyeOffsets[i] = controller::Pose(eyePosition, eyeOrientation).getMatrix(); + } + + _lastViewState = { .type = XR_TYPE_VIEW_STATE }; + + XrViewLocateInfo viewLocateInfo = { + .type = XR_TYPE_VIEW_LOCATE_INFO, + .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + .displayTime = _lastFrameState.predictedDisplayTime, + .space = _context->_stageSpace, + }; + + result = xrLocateViews(_context->_session, &viewLocateInfo, &_lastViewState, _viewCount, &_viewCount, _views.value().data()); + if (!xrCheck(_context->_instance, result, "Could not locate views")) + return; + + for (uint32_t i = 0; i < _viewCount; i++) { + _projectionLayerViews[i].pose = _views.value()[i].pose; + _projectionLayerViews[i].fov = _views.value()[i].fov; + } + + XrSpaceLocation headLocation = { + .type = XR_TYPE_SPACE_LOCATION, + .pose = XR_INDENTITY_POSE, + }; + xrLocateSpace(_context->_viewSpace, _context->_stageSpace, _lastFrameState.predictedDisplayTime, &headLocation); + + glm::vec3 headPosition = xrVecToGlm(headLocation.pose.position); + glm::quat headOrientation = xrQuatToGlm(headLocation.pose.orientation); + _context->_lastHeadPose = controller::Pose(headPosition, headOrientation); + + _currentRenderFrameInfo = FrameInfo(); + _currentRenderFrameInfo.renderPose = _context->_lastHeadPose.getMatrix(); + _currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; + + _currentPresentFrameInfo.renderPose = _currentRenderFrameInfo.renderPose; + _currentPresentFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; } int OpenXrDisplayPlugin::getRequiredThreadCount() const { diff --git a/plugins/openxr/src/OpenXrDisplayPlugin.h b/plugins/openxr/src/OpenXrDisplayPlugin.h index aef27c3cb2..01df5fcf27 100644 --- a/plugins/openxr/src/OpenXrDisplayPlugin.h +++ b/plugins/openxr/src/OpenXrDisplayPlugin.h @@ -26,8 +26,6 @@ public: void init() override; - void idle() override; - float getTargetFrameRate() const override; bool hasAsyncReprojection() const override { return true; } @@ -86,7 +84,4 @@ private: bool _haveFrameToSubmit = false; std::mutex _haveFrameMutex; - - float _lastFrameTime; - float _estimatedTargetFramerate; }; diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index 710a3afc65..bb898a2e3f 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -56,6 +56,8 @@ QString OpenXrInputPlugin::configurationLayout() { bool OpenXrInputPlugin::activate() { InputPlugin::activate(); + qCCritical(xr_input_cat) << "OpenXrInputPlugin::activate"; + loadSettings(); // register with UserInputMapper @@ -69,6 +71,8 @@ bool OpenXrInputPlugin::activate() { void OpenXrInputPlugin::deactivate() { InputPlugin::deactivate(); + qCCritical(xr_input_cat) << "OpenXrInputPlugin::deactivate"; + _inputDevice->_poseStateMap.clear(); // unregister with UserInputMapper @@ -117,6 +121,10 @@ OpenXrInputPlugin::InputDevice::InputDevice(std::shared_ptr c) : OpenXrInputPlugin::InputDevice::~InputDevice() { if (_handTracker[0] != XR_NULL_HANDLE) { _context->xrDestroyHandTrackerEXT(_handTracker[0]); } if (_handTracker[1] != XR_NULL_HANDLE) { _context->xrDestroyHandTrackerEXT(_handTracker[1]); } + + // so we don't accidentally try to destroy them twice + _handTracker[0] = XR_NULL_HANDLE; + _handTracker[1] = XR_NULL_HANDLE; } void OpenXrInputPlugin::InputDevice::focusOutEvent() { @@ -141,6 +149,9 @@ bool OpenXrInputPlugin::InputDevice::triggerHapticPulse(float strength, float du auto path = (index == 0) ? "left_haptic" : "right_haptic"; + // FIXME: sometimes something bugs out and hammers this, + // and the controller vibrates really loudly until another + // haptic pulse is triggered if (!_actions.at(path)->applyHaptic(xrDuration, XR_FREQUENCY_UNSPECIFIED, 0.5f * strength)) { qCCritical(xr_input_cat) << "Failed to apply haptic feedback!"; } diff --git a/scripts/system/controllers/toggleAdvancedMovementForHandControllers.js b/scripts/system/controllers/toggleAdvancedMovementForHandControllers.js index b0b498fc29..d72cdd7cd9 100644 --- a/scripts/system/controllers/toggleAdvancedMovementForHandControllers.js +++ b/scripts/system/controllers/toggleAdvancedMovementForHandControllers.js @@ -51,9 +51,7 @@ return; } - if (value === 1 && Controller.Hardware.OculusTouch !== undefined) { - rotate180(); - } else if (Controller.Hardware.Vive !== undefined) { + if (Controller.Hardware.Vive !== undefined) { if (value > 0.75 && inFlipTurn === false) { inFlipTurn = true; rotate180(); @@ -61,6 +59,8 @@ inFlipTurn = false; }, TURN_RATE); } + } else { + rotate180(); } return; }); @@ -71,9 +71,7 @@ return; } - if (value === 1 && Controller.Hardware.OculusTouch !== undefined) { - rotate180(); - } else if (Controller.Hardware.Vive !== undefined) { + if (Controller.Hardware.Vive !== undefined) { if (value > 0.75 && inFlipTurn === false) { inFlipTurn = true; rotate180(); @@ -81,6 +79,8 @@ inFlipTurn = false; }, TURN_RATE); } + } else { + rotate180(); } return; }); @@ -96,7 +96,7 @@ registerBasicMapping(); Script.setTimeout(function() { - if (MyAvatar.useAdvanceMovementControls) { + if (MyAvatar.useAdvancedMovementControls) { Controller.disableMapping(DRIVING_MAPPING_NAME); } else { Controller.enableMapping(DRIVING_MAPPING_NAME); @@ -112,7 +112,7 @@ HMD.displayModeChanged.connect(function(isHMDMode) { if (isHMDMode) { - if (Controller.Hardware.Vive !== undefined || Controller.Hardware.OculusTouch !== undefined) { + if (Controller.Hardware.OpenXR !== undefined || Controller.Hardware.Vive !== undefined || Controller.Hardware.OculusTouch !== undefined) { if (MyAvatar.useAdvancedMovementControls) { Controller.disableMapping(DRIVING_MAPPING_NAME); } else { @@ -131,7 +131,7 @@ function update() { - if ((Controller.Hardware.Vive !== undefined || Controller.Hardware.OculusTouch !== undefined) && HMD.active) { + if ((Controller.Hardware.OpenXR !== undefined || Controller.Hardware.Vive !== undefined || Controller.Hardware.OculusTouch !== undefined) && HMD.active) { var flying = MyAvatar.getFlyingEnabled(); var driving = MyAvatar.useAdvancedMovementControls; From 1a2613089686c30076d66c840419f06a85448846 Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 4 May 2025 03:18:54 +1000 Subject: [PATCH 2/5] Don't double-destroy session --- plugins/openxr/src/OpenXrContext.cpp | 34 +++++++++++++++++------- plugins/openxr/src/OpenXrInputPlugin.cpp | 14 +--------- plugins/openxr/src/OpenXrInputPlugin.h | 2 -- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/plugins/openxr/src/OpenXrContext.cpp b/plugins/openxr/src/OpenXrContext.cpp index f18c30bbef..264c4c5251 100644 --- a/plugins/openxr/src/OpenXrContext.cpp +++ b/plugins/openxr/src/OpenXrContext.cpp @@ -8,7 +8,7 @@ // #include "OpenXrContext.h" -#include +#include #include #include @@ -67,8 +67,10 @@ OpenXrContext::~OpenXrContext() { } bool OpenXrContext::initInstance() { - if (static_cast(qApp)->platformName() == "wayland") { - qCCritical(xr_context_cat, "The OpenXR plugin does not support Wayland yet! Use the QT_QPA_PLATFORM=xcb environment variable to force Overte to launch with X11."); + auto myApp = static_cast(qApp); + if (myApp->platformName() == "wayland") { + auto msg = QString::fromUtf8("The OpenXR plugin does not support Wayland yet! Use the QT_QPA_PLATFORM=xcb environment variable to force Overte to launch with X11."); + qCCritical(xr_context_cat) << msg; return false; } @@ -258,32 +260,45 @@ bool OpenXrContext::initGraphics() { } bool OpenXrContext::requestExitSession() { + if (_session == XR_NULL_HANDLE) { return true; } + XrResult result = xrRequestExitSession(_session); return xrCheck(_instance, result, "Failed to request exit session!"); } bool OpenXrContext::initSession() { + if (_session != XR_NULL_HANDLE) { return true; } + + XrSessionCreateInfo info = { + .type = XR_TYPE_SESSION_CREATE_INFO, + .next = nullptr, + .systemId = _systemId, + }; + #if defined(Q_OS_LINUX) - XrGraphicsBindingOpenGLXlibKHR binding = { + // if (wayland) { + // blah blah... + // info.next = &wlBinding; + // } else + XrGraphicsBindingOpenGLXlibKHR xlibBinding = { .type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR, .xDisplay = XOpenDisplay(nullptr), .glxDrawable = glXGetCurrentDrawable(), .glxContext = glXGetCurrentContext(), }; + + info.next = &xlibBinding; #elif defined(Q_OS_WIN) XrGraphicsBindingOpenGLWin32KHR binding = { .type = XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR, .hDC = wglGetCurrentDC(), .hGLRC = wglGetCurrentContext(), }; + + info.next = &binding; #else #error "Unsupported platform" #endif - XrSessionCreateInfo info = { - .type = XR_TYPE_SESSION_CREATE_INFO, - .next = &binding, - .systemId = _systemId, - }; XrResult result = xrCreateSession(_instance, &info, &_session); return xrCheck(_instance, result, "Failed to create session"); @@ -398,6 +413,7 @@ bool OpenXrContext::updateSessionState(XrSessionState newState) { return false; _shouldQuit = true; _shouldRunFrameCycle = false; + _session = XR_NULL_HANDLE; qCDebug(xr_context_cat, "Destroyed session"); break; } diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index bb898a2e3f..5afde0af7b 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -56,8 +56,6 @@ QString OpenXrInputPlugin::configurationLayout() { bool OpenXrInputPlugin::activate() { InputPlugin::activate(); - qCCritical(xr_input_cat) << "OpenXrInputPlugin::activate"; - loadSettings(); // register with UserInputMapper @@ -71,8 +69,6 @@ bool OpenXrInputPlugin::activate() { void OpenXrInputPlugin::deactivate() { InputPlugin::deactivate(); - qCCritical(xr_input_cat) << "OpenXrInputPlugin::deactivate"; - _inputDevice->_poseStateMap.clear(); // unregister with UserInputMapper @@ -118,15 +114,6 @@ OpenXrInputPlugin::InputDevice::InputDevice(std::shared_ptr c) : qCInfo(xr_input_cat) << "Hand tracking supported:" << _context->_handTrackingSupported; } -OpenXrInputPlugin::InputDevice::~InputDevice() { - if (_handTracker[0] != XR_NULL_HANDLE) { _context->xrDestroyHandTrackerEXT(_handTracker[0]); } - if (_handTracker[1] != XR_NULL_HANDLE) { _context->xrDestroyHandTrackerEXT(_handTracker[1]); } - - // so we don't accidentally try to destroy them twice - _handTracker[0] = XR_NULL_HANDLE; - _handTracker[1] = XR_NULL_HANDLE; -} - void OpenXrInputPlugin::InputDevice::focusOutEvent() { _axisStateMap.clear(); _buttonPressedMap.clear(); @@ -152,6 +139,7 @@ bool OpenXrInputPlugin::InputDevice::triggerHapticPulse(float strength, float du // FIXME: sometimes something bugs out and hammers this, // and the controller vibrates really loudly until another // haptic pulse is triggered + // The OpenVR plugin has a lock protecting these if (!_actions.at(path)->applyHaptic(xrDuration, XR_FREQUENCY_UNSPECIFIED, 0.5f * strength)) { qCCritical(xr_input_cat) << "Failed to apply haptic feedback!"; } diff --git a/plugins/openxr/src/OpenXrInputPlugin.h b/plugins/openxr/src/OpenXrInputPlugin.h index 53866002ee..a3ea3a85de 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.h +++ b/plugins/openxr/src/OpenXrInputPlugin.h @@ -75,8 +75,6 @@ private: public: InputDevice(std::shared_ptr c); - ~InputDevice(); - private: controller::Input::NamedVector getAvailableInputs() const override; QString getDefaultMappingConfig() const override; From 818f6696f402e70309ef685821805ddf2b120c30 Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 4 May 2025 21:52:08 +1000 Subject: [PATCH 3/5] Stutter fix --- interface/resources/controllers/openxr.json | 4 +-- .../display-plugins/OpenGLDisplayPlugin.cpp | 7 ---- .../src/display-plugins/OpenGLDisplayPlugin.h | 2 -- libraries/shared/src/Transform.h | 6 +++- plugins/openxr/src/OpenXrDisplayPlugin.cpp | 32 +++++++++++-------- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/interface/resources/controllers/openxr.json b/interface/resources/controllers/openxr.json index 5d8b6d089c..469605d167 100644 --- a/interface/resources/controllers/openxr.json +++ b/interface/resources/controllers/openxr.json @@ -11,8 +11,8 @@ { "from": "OpenXR.LeftGrip", "to": "Standard.LeftGrip", "filters": [{ "type": "deadZone", "min": 0.05 }] }, { "from": "OpenXR.RightGrip", "to": "Standard.RightGrip", "filters": [{ "type": "deadZone", "min": 0.05 }] }, - { "from": "OpenXR.LX", "to": "Standard.LX" }, - { "from": "OpenXR.LY", "to": "Standard.LY" }, + { "from": "OpenXR.LX", "to": "Standard.LX", "filters": [{ "type": "deadZone", "min": 0.05 }] }, + { "from": "OpenXR.LY", "to": "Standard.LY", "filters": [{ "type": "deadZone", "min": 0.05 }] }, { "from": "OpenXR.RX", "to": "Standard.RX" }, { "from": "OpenXR.RY", "to": "Standard.RY" }, diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 2943ff465b..d5ce52de18 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -745,13 +745,6 @@ void OpenGLDisplayPlugin::present(const std::shared_ptr& } gpu::Backend::freeGPUMemSize.set(gpu::gl::getFreeDedicatedMemory()); - - // Drop current frame after presenting it once. - // This is required for the OpenXR frame cycle, since we call xrEndFrame after presenting. - // xrEndFrame must not be called multiple times. - if (_presentOnlyOnce) { - _currentFrame.reset(); - } } else if (alwaysPresent()) { refreshRateController->clockEndTime(); internalPresent(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 2048c1498f..4dc10a7aa1 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -205,8 +205,6 @@ protected: QImage getScreenshot(float aspectRatio); QImage getSecondaryCameraScreenshot(); - bool _presentOnlyOnce = false; - private: static Setting::Handle _extraLinearToSRGBConversionSetting; }; diff --git a/libraries/shared/src/Transform.h b/libraries/shared/src/Transform.h index c0c0a7ed94..770515dacf 100644 --- a/libraries/shared/src/Transform.h +++ b/libraries/shared/src/Transform.h @@ -455,7 +455,11 @@ inline Transform::Mat4& Transform::getRotationScaleMatrixInverse(Mat4& result) c inline Transform& Transform::evalFromRawMatrix(const Mat4& matrix) { // for now works only in the case of TRS transformation - if ((matrix[0][3] == 0.0f) && (matrix[1][3] == 0.0f) && (matrix[2][3] == 0.0f) && (matrix[3][3] == 1.0f)) { + constexpr float MATRIX_CHECK_EPSILON = 0.0001f; + if ((fabsf(matrix[0][3]) <= MATRIX_CHECK_EPSILON) + && (fabsf(matrix[1][3]) <= MATRIX_CHECK_EPSILON) + && (fabsf(matrix[2][3]) <= MATRIX_CHECK_EPSILON) + && (fabsf(1.0f - matrix[3][3]) <= MATRIX_CHECK_EPSILON)) { setTranslation(extractTranslation(matrix)); evalFromRawMatrix(Mat3(matrix)); } diff --git a/plugins/openxr/src/OpenXrDisplayPlugin.cpp b/plugins/openxr/src/OpenXrDisplayPlugin.cpp index 927d5f6668..0faa90cd3d 100644 --- a/plugins/openxr/src/OpenXrDisplayPlugin.cpp +++ b/plugins/openxr/src/OpenXrDisplayPlugin.cpp @@ -81,12 +81,9 @@ glm::mat4 OpenXrDisplayPlugin::getCullingProjection(const glm::mat4& baseProject return getEyeProjection(Left, baseProjection); } -// OpenXR doesn't give us a target framerate, -// but it does do vsync on its own, -// so just push out frames as vsync allows float OpenXrDisplayPlugin::getTargetFrameRate() const { // predictedDisplayPeriod is delta nanoseconds, so convert it to frames per second - return std::min(1.0f, 1.0f / (_lastFrameState.predictedDisplayPeriod / 1e9f)); + return std::max(1.0f, 1.0f / (_lastFrameState.predictedDisplayPeriod / 1e9f)); } bool OpenXrDisplayPlugin::initViews() { @@ -354,7 +351,14 @@ bool OpenXrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } } - _frameInfos[frameIndex] = _currentRenderFrameInfo; + _currentRenderFrameInfo = FrameInfo(); + _currentRenderFrameInfo.predictedDisplayTime = _lastFrameState.predictedDisplayTime / 1e9; + + withNonPresentThreadLock([&] { + _currentRenderFrameInfo.renderPose = _context->_lastHeadPose.getMatrix(); + _currentRenderFrameInfo.presentPose = _context->_lastHeadPose.getMatrix(); + _frameInfos[frameIndex] = _currentRenderFrameInfo; + }); return HmdDisplayPlugin::beginFrameRender(frameIndex); } @@ -486,6 +490,10 @@ bool OpenXrDisplayPlugin::isHmdMounted() const { void OpenXrDisplayPlugin::updatePresentPose() { _context->_lastPredictedDisplayTime = _lastFrameState.predictedDisplayTime; + // it *feels* like we need two frames of prediction for + // it to feel good, the oculusMobilePlugin does the same + auto twoFramesLater = _lastFrameState.predictedDisplayTime + (_lastFrameState.predictedDisplayPeriod * 2); + std::vector eye_views(_viewCount); for (uint32_t i = 0; i < _viewCount; i++) { eye_views[i].type = XR_TYPE_VIEW; @@ -495,7 +503,7 @@ void OpenXrDisplayPlugin::updatePresentPose() { XrViewLocateInfo eyeViewLocateInfo = { .type = XR_TYPE_VIEW_LOCATE_INFO, .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, - .displayTime = _lastFrameState.predictedDisplayTime, + .displayTime = twoFramesLater, .space = _context->_viewSpace, }; @@ -516,7 +524,7 @@ void OpenXrDisplayPlugin::updatePresentPose() { XrViewLocateInfo viewLocateInfo = { .type = XR_TYPE_VIEW_LOCATE_INFO, .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, - .displayTime = _lastFrameState.predictedDisplayTime, + .displayTime = twoFramesLater, .space = _context->_stageSpace, }; @@ -533,18 +541,14 @@ void OpenXrDisplayPlugin::updatePresentPose() { .type = XR_TYPE_SPACE_LOCATION, .pose = XR_INDENTITY_POSE, }; - xrLocateSpace(_context->_viewSpace, _context->_stageSpace, _lastFrameState.predictedDisplayTime, &headLocation); + xrLocateSpace(_context->_viewSpace, _context->_stageSpace, twoFramesLater, &headLocation); glm::vec3 headPosition = xrVecToGlm(headLocation.pose.position); glm::quat headOrientation = xrQuatToGlm(headLocation.pose.orientation); _context->_lastHeadPose = controller::Pose(headPosition, headOrientation); - _currentRenderFrameInfo = FrameInfo(); - _currentRenderFrameInfo.renderPose = _context->_lastHeadPose.getMatrix(); - _currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; - - _currentPresentFrameInfo.renderPose = _currentRenderFrameInfo.renderPose; - _currentPresentFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; + _currentPresentFrameInfo.presentPose = _context->_lastHeadPose.getMatrix(); + _currentPresentFrameInfo.predictedDisplayTime = _lastFrameState.predictedDisplayTime / 1e9; } int OpenXrDisplayPlugin::getRequiredThreadCount() const { From 4e0649cc743855476477aa2eab9caddb26ea94ec Mon Sep 17 00:00:00 2001 From: Ada Date: Mon, 5 May 2025 17:48:18 +1000 Subject: [PATCH 4/5] Fix OpenXR view swimming, increase grip deadzone --- interface/resources/controllers/openxr.json | 8 ++--- plugins/openxr/src/OpenXrDisplayPlugin.cpp | 39 ++++----------------- plugins/openxr/src/OpenXrInputPlugin.cpp | 3 ++ 3 files changed, 13 insertions(+), 37 deletions(-) diff --git a/interface/resources/controllers/openxr.json b/interface/resources/controllers/openxr.json index 469605d167..9801434298 100644 --- a/interface/resources/controllers/openxr.json +++ b/interface/resources/controllers/openxr.json @@ -3,13 +3,13 @@ "channels": [ { "from": "OpenXR.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] }, - { "from": "OpenXR.LT", "to": "Standard.LT", "filters": [{"type": "deadZone", "min": 0.05}]}, - { "from": "OpenXR.RT", "to": "Standard.RT", "filters": [{"type": "deadZone", "min": 0.05}]}, + { "from": "OpenXR.LT", "to": "Standard.LT", "filters": [{"type": "deadZone", "min": 0.05}] }, + { "from": "OpenXR.RT", "to": "Standard.RT", "filters": [{"type": "deadZone", "min": 0.05}] }, { "from": "OpenXR.LTClick", "to": "Standard.LTClick" }, { "from": "OpenXR.RTClick", "to": "Standard.RTClick" }, - { "from": "OpenXR.LeftGrip", "to": "Standard.LeftGrip", "filters": [{ "type": "deadZone", "min": 0.05 }] }, - { "from": "OpenXR.RightGrip", "to": "Standard.RightGrip", "filters": [{ "type": "deadZone", "min": 0.05 }] }, + { "from": "OpenXR.LeftGrip", "to": "Standard.LeftGrip", "filters": [{ "type": "deadZone", "min": 0.6 }] }, + { "from": "OpenXR.RightGrip", "to": "Standard.RightGrip", "filters": [{ "type": "deadZone", "min": 0.6 }] }, { "from": "OpenXR.LX", "to": "Standard.LX", "filters": [{ "type": "deadZone", "min": 0.05 }] }, { "from": "OpenXR.LY", "to": "Standard.LY", "filters": [{ "type": "deadZone", "min": 0.05 }] }, diff --git a/plugins/openxr/src/OpenXrDisplayPlugin.cpp b/plugins/openxr/src/OpenXrDisplayPlugin.cpp index 0faa90cd3d..24448e83a4 100644 --- a/plugins/openxr/src/OpenXrDisplayPlugin.cpp +++ b/plugins/openxr/src/OpenXrDisplayPlugin.cpp @@ -335,22 +335,6 @@ bool OpenXrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { return true; } - // Wait for present thread - // Actually wait for xrEndFrame to happen. - bool haveFrameToSubmit = true; - { - std::unique_lock lock(_haveFrameMutex); - haveFrameToSubmit = _haveFrameToSubmit; - } - - while (haveFrameToSubmit) { - std::this_thread::sleep_for(std::chrono::microseconds(10)); - { - std::unique_lock lock(_haveFrameMutex); - haveFrameToSubmit = _haveFrameToSubmit; - } - } - _currentRenderFrameInfo = FrameInfo(); _currentRenderFrameInfo.predictedDisplayTime = _lastFrameState.predictedDisplayTime / 1e9; @@ -365,10 +349,6 @@ bool OpenXrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { void OpenXrDisplayPlugin::submitFrame(const gpu::FramePointer& newFrame) { OpenGLDisplayPlugin::submitFrame(newFrame); - { - std::unique_lock lock(_haveFrameMutex); - _haveFrameToSubmit = true; - } } void OpenXrDisplayPlugin::compositeLayers() { @@ -398,8 +378,6 @@ void OpenXrDisplayPlugin::hmdPresent() { if (!_context->beginFrame()) return; - updatePresentPose(); - if (_lastFrameState.shouldRender) { // TODO: Use multiview swapchain for (uint32_t i = 0; i < 2; i++) { @@ -436,11 +414,6 @@ void OpenXrDisplayPlugin::hmdPresent() { endFrame(); _presentRate.increment(); - - { - std::unique_lock lock(_haveFrameMutex); - _haveFrameToSubmit = false; - } } bool OpenXrDisplayPlugin::endFrame() { @@ -488,11 +461,11 @@ bool OpenXrDisplayPlugin::isHmdMounted() const { } void OpenXrDisplayPlugin::updatePresentPose() { + if (_lastFrameState.predictedDisplayTime == 0) { return; } + _context->_lastPredictedDisplayTime = _lastFrameState.predictedDisplayTime; - // it *feels* like we need two frames of prediction for - // it to feel good, the oculusMobilePlugin does the same - auto twoFramesLater = _lastFrameState.predictedDisplayTime + (_lastFrameState.predictedDisplayPeriod * 2); + auto predictedDisplayTime = _lastFrameState.predictedDisplayTime; std::vector eye_views(_viewCount); for (uint32_t i = 0; i < _viewCount; i++) { @@ -503,7 +476,7 @@ void OpenXrDisplayPlugin::updatePresentPose() { XrViewLocateInfo eyeViewLocateInfo = { .type = XR_TYPE_VIEW_LOCATE_INFO, .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, - .displayTime = twoFramesLater, + .displayTime = predictedDisplayTime, .space = _context->_viewSpace, }; @@ -524,7 +497,7 @@ void OpenXrDisplayPlugin::updatePresentPose() { XrViewLocateInfo viewLocateInfo = { .type = XR_TYPE_VIEW_LOCATE_INFO, .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, - .displayTime = twoFramesLater, + .displayTime = predictedDisplayTime, .space = _context->_stageSpace, }; @@ -541,7 +514,7 @@ void OpenXrDisplayPlugin::updatePresentPose() { .type = XR_TYPE_SPACE_LOCATION, .pose = XR_INDENTITY_POSE, }; - xrLocateSpace(_context->_viewSpace, _context->_stageSpace, twoFramesLater, &headLocation); + xrLocateSpace(_context->_viewSpace, _context->_stageSpace, predictedDisplayTime, &headLocation); glm::vec3 headPosition = xrVecToGlm(headLocation.pose.position); glm::quat headOrientation = xrQuatToGlm(headLocation.pose.orientation); diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index 5afde0af7b..47dfd9bcd3 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -417,6 +417,9 @@ bool OpenXrInputPlugin::InputDevice::initActions() { if (!xrCheck(instance, result, "Failed to create action set.")) return false; + // NOTE: The "squeeze" actions have quite a high deadzone in the controller config. + // A lot of our controller scripts currently only check for (squeeze > 0), + // which means controllers like the Index ones will be way too sensitive. std::map> actionTypes = { {"left_primary_click", {"Left Primary", XR_ACTION_TYPE_BOOLEAN_INPUT}}, {"left_secondary_click", {"Left Secondary (Tablet)", XR_ACTION_TYPE_BOOLEAN_INPUT}}, From f961e3e303370c53158e54ad05a1fe12534fcf53 Mon Sep 17 00:00:00 2001 From: Ada Date: Mon, 5 May 2025 18:17:26 +1000 Subject: [PATCH 5/5] Revert grip deadzone, was only an issue for the VR keyboard --- interface/resources/controllers/openxr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/controllers/openxr.json b/interface/resources/controllers/openxr.json index 9801434298..76d84cb8eb 100644 --- a/interface/resources/controllers/openxr.json +++ b/interface/resources/controllers/openxr.json @@ -8,8 +8,8 @@ { "from": "OpenXR.LTClick", "to": "Standard.LTClick" }, { "from": "OpenXR.RTClick", "to": "Standard.RTClick" }, - { "from": "OpenXR.LeftGrip", "to": "Standard.LeftGrip", "filters": [{ "type": "deadZone", "min": 0.6 }] }, - { "from": "OpenXR.RightGrip", "to": "Standard.RightGrip", "filters": [{ "type": "deadZone", "min": 0.6 }] }, + { "from": "OpenXR.LeftGrip", "to": "Standard.LeftGrip", "filters": [{ "type": "deadZone", "min": 0.05 }] }, + { "from": "OpenXR.RightGrip", "to": "Standard.RightGrip", "filters": [{ "type": "deadZone", "min": 0.05 }] }, { "from": "OpenXR.LX", "to": "Standard.LX", "filters": [{ "type": "deadZone", "min": 0.05 }] }, { "from": "OpenXR.LY", "to": "Standard.LY", "filters": [{ "type": "deadZone", "min": 0.05 }] },