From 881fbf5641e3cb5728b76f00e59392b225d8be2f Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Sat, 25 May 2024 13:18:31 +0200 Subject: [PATCH 01/43] ports: Add jsoncpp. The build demands it but it's missing. Copy from upstream vcpkg repo. Fixes the following error: ``` Computing installation plan... error: while looking for jsoncpp:x64-linux: overte-files/vcpkg/cfe0a2a0/ports/jsoncpp: error: jsoncpp does not exist ``` --- cmake/ports/jsoncpp/portfile.cmake | 34 ++++++++++++++++++++++++++++++ cmake/ports/jsoncpp/vcpkg.json | 18 ++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 cmake/ports/jsoncpp/portfile.cmake create mode 100644 cmake/ports/jsoncpp/vcpkg.json diff --git a/cmake/ports/jsoncpp/portfile.cmake b/cmake/ports/jsoncpp/portfile.cmake new file mode 100644 index 0000000000..e257f7637d --- /dev/null +++ b/cmake/ports/jsoncpp/portfile.cmake @@ -0,0 +1,34 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO open-source-parsers/jsoncpp + REF "${VERSION}" + SHA512 1d06e044759b1e1a4cc4960189dd7e001a0a4389d7239a6d59295af995a553518e4e0337b4b4b817e70da5d9731a4c98655af90791b6287870b5ff8d73ad8873 + HEAD_REF master +) + +string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" JSONCPP_STATIC) +string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" STATIC_CRT) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + -DJSONCPP_WITH_CMAKE_PACKAGE=ON + -DBUILD_STATIC_LIBS=${JSONCPP_STATIC} + -DJSONCPP_STATIC_WINDOWS_RUNTIME=${STATIC_CRT} + -DJSONCPP_WITH_PKGCONFIG_SUPPORT=ON + -DJSONCPP_WITH_POST_BUILD_UNITTEST=OFF + -DJSONCPP_WITH_TESTS=OFF + -DJSONCPP_WITH_EXAMPLE=OFF + -DBUILD_OBJECT_LIBS=OFF +) + +vcpkg_cmake_install() + +vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/jsoncpp) + +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") + +vcpkg_copy_pdbs() +vcpkg_fixup_pkgconfig() + +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE") diff --git a/cmake/ports/jsoncpp/vcpkg.json b/cmake/ports/jsoncpp/vcpkg.json new file mode 100644 index 0000000000..878449f0af --- /dev/null +++ b/cmake/ports/jsoncpp/vcpkg.json @@ -0,0 +1,18 @@ +{ + "name": "jsoncpp", + "version": "1.9.5", + "port-version": 4, + "description": "JsonCpp is a C++ library that allows manipulating JSON values, including serialization and deserialization to and from strings. It can also preserve existing comment in unserialization/serialization steps, making it a convenient format to store user input files.", + "homepage": "https://github.com/open-source-parsers/jsoncpp", + "license": "MIT", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ] +} From 3d51b10bcb9b0ba5837b2d49955389ee19b81a15 Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Sat, 25 May 2024 11:56:45 +0200 Subject: [PATCH 02/43] ports: Add openxr-loader. Copy from vcpkg repository. Add OpenXR to hifi-client-deps. --- cmake/ports/hifi-client-deps/CONTROL | 2 +- cmake/ports/openxr-loader/fix-jinja2.patch | 23 ++++++ .../fix-openxr-sdk-jsoncpp.patch | 30 +++++++ cmake/ports/openxr-loader/portfile.cmake | 79 +++++++++++++++++++ .../python3_8_compatibility.patch | 13 +++ cmake/ports/openxr-loader/vcpkg.json | 27 +++++++ 6 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 cmake/ports/openxr-loader/fix-jinja2.patch create mode 100644 cmake/ports/openxr-loader/fix-openxr-sdk-jsoncpp.patch create mode 100644 cmake/ports/openxr-loader/portfile.cmake create mode 100644 cmake/ports/openxr-loader/python3_8_compatibility.patch create mode 100644 cmake/ports/openxr-loader/vcpkg.json diff --git a/cmake/ports/hifi-client-deps/CONTROL b/cmake/ports/hifi-client-deps/CONTROL index afee6a5d48..c951768a4d 100644 --- a/cmake/ports/hifi-client-deps/CONTROL +++ b/cmake/ports/hifi-client-deps/CONTROL @@ -1,4 +1,4 @@ Source: hifi-client-deps Version: 0.1 Description: Collected dependencies for High Fidelity applications -Build-Depends: hifi-deps, aristo (windows), glslang, liblo (windows), nlohmann-json, openvr ((linux&!arm)|windows), quazip (!android), sdl2 (!android), spirv-cross (!android), spirv-tools (!android), sranipal (windows), vulkanmemoryallocator, discord-rpc (!android) +Build-Depends: hifi-deps, aristo (windows), glslang, liblo (windows), nlohmann-json, openvr ((linux&!arm)|windows), openxr-loader, quazip (!android), sdl2 (!android), spirv-cross (!android), spirv-tools (!android), sranipal (windows), vulkanmemoryallocator, discord-rpc (!android) diff --git a/cmake/ports/openxr-loader/fix-jinja2.patch b/cmake/ports/openxr-loader/fix-jinja2.patch new file mode 100644 index 0000000000..5d77cb4e46 --- /dev/null +++ b/cmake/ports/openxr-loader/fix-jinja2.patch @@ -0,0 +1,23 @@ +From d80c7dc3f4810fc49e4444590d39ef71e8a9b01c Mon Sep 17 00:00:00 2001 +From: Adam Johnson +Date: Sat, 19 Feb 2022 19:42:31 -0500 +Subject: [PATCH] Fix bad import in jinja2 + +--- + external/python/jinja2/utils.py | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/external/python/jinja2/utils.py b/external/python/jinja2/utils.py +index db9c5d06..f198e3ef 100644 +--- a/external/python/jinja2/utils.py ++++ b/external/python/jinja2/utils.py +@@ -639,4 +639,8 @@ def __repr__(self): + + + # Imported here because that's where it was in the past +-from markupsafe import Markup, escape, soft_unicode ++from markupsafe import Markup, escape ++try: ++ from markupsafe import soft_unicode ++except ImportError: ++ from markupsafe import soft_str as soft_unicode diff --git a/cmake/ports/openxr-loader/fix-openxr-sdk-jsoncpp.patch b/cmake/ports/openxr-loader/fix-openxr-sdk-jsoncpp.patch new file mode 100644 index 0000000000..758d55e0f8 --- /dev/null +++ b/cmake/ports/openxr-loader/fix-openxr-sdk-jsoncpp.patch @@ -0,0 +1,30 @@ +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index c75b145..386494c 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -89,7 +89,7 @@ if(NOT VULKAN_INCOMPATIBLE) + endif() + + find_package(Threads REQUIRED) +-find_package(JsonCpp) ++find_package(jsoncpp CONFIG REQUIRED) + + ### All options defined here + option(BUILD_LOADER "Build loader" ON) +diff --git a/src/loader/CMakeLists.txt b/src/loader/CMakeLists.txt +index 6a88cf4..0821a3d 100644 +--- a/src/loader/CMakeLists.txt ++++ b/src/loader/CMakeLists.txt +@@ -68,7 +68,11 @@ add_library(openxr_loader ${LIBRARY_TYPE} + ${openxr_loader_RESOURCE_FILE} + ) + if(BUILD_WITH_SYSTEM_JSONCPP) +- target_link_libraries(openxr_loader PRIVATE JsonCpp::JsonCpp) ++ if(BUILD_SHARED_LIBS) ++ target_link_libraries(openxr_loader PRIVATE jsoncpp_lib) ++ else() ++ target_link_libraries(openxr_loader PRIVATE jsoncpp_static) ++ endif() + else() + target_sources(openxr_loader + PRIVATE diff --git a/cmake/ports/openxr-loader/portfile.cmake b/cmake/ports/openxr-loader/portfile.cmake new file mode 100644 index 0000000000..4d1127e56e --- /dev/null +++ b/cmake/ports/openxr-loader/portfile.cmake @@ -0,0 +1,79 @@ + +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO KhronosGroup/OpenXR-SDK + REF "release-${VERSION}" + SHA512 6efc7596e707f95366dbcdbac9bd7d0c20735a2175b4edf56a9e8a112cf0ab8b664069fe942313164a37119032ddbf5671bc88ab5f276005dd36e4a4dabba1c7 + HEAD_REF master + PATCHES + fix-openxr-sdk-jsoncpp.patch +) + +vcpkg_from_github( + OUT_SOURCE_PATH SDK_SOURCE_PATH + REPO KhronosGroup/OpenXR-SDK-Source + REF "release-${VERSION}" + SHA512 04bdb0f16078209b5edd175a3396f70e1ceb8cfa382c65b8fda388e565480e3844daf68e0d987e72ed8c21d3148af0b41a2170911ec1660565887e0e5ae6d2bf + HEAD_REF master + PATCHES + fix-openxr-sdk-jsoncpp.patch + fix-jinja2.patch +) + +vcpkg_from_github( + OUT_SOURCE_PATH HPP_SOURCE_PATH + REPO KhronosGroup/OpenXR-hpp + REF 63db9919822f8af6f7bf7416ba6a015d4617202e + SHA512 9e768f485d1631f8e74f35f028a64e2d64e33d362c53ae1c54427a10786e3befdd24089927319aa1a4b4c3e010247bd6cb3394bcee460c467c637ab6bc7bec90 + HEAD_REF master + PATCHES + python3_8_compatibility.patch +) + +# Weird behavior inside the OpenXR loader. On Windows they force shared libraries to use static crt, and +# vice-versa. Might be better in future iterations to patch the CMakeLists.txt for OpenXR +if (VCPKG_TARGET_IS_UWP OR VCPKG_TARGET_IS_WINDOWS) + if(VCPKG_LIBRARY_LINKAGE STREQUAL static) + set(DYNAMIC_LOADER OFF) + set(VCPKG_CRT_LINKAGE dynamic) + else() + set(DYNAMIC_LOADER ON) + set(VCPKG_CRT_LINKAGE static) + endif() +endif() + +vcpkg_find_acquire_program(PYTHON3) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + -DBUILD_API_LAYERS=OFF + -DBUILD_TESTS=OFF + -DBUILD_CONFORMANCE_TESTS=OFF + -DDYNAMIC_LOADER=${DYNAMIC_LOADER} + -DPYTHON_EXECUTABLE="${PYTHON3}" + -DBUILD_WITH_SYSTEM_JSONCPP=ON +) + +vcpkg_cmake_install() + +# Generate the OpenXR C++ bindings +set(ENV{OPENXR_REPO} "${SDK_SOURCE_PATH}") +vcpkg_execute_required_process( + COMMAND ${PYTHON3} "${HPP_SOURCE_PATH}/scripts/hpp_genxr.py" -quiet -registry "${SDK_SOURCE_PATH}/specification/registry/xr.xml" -o "${CURRENT_PACKAGES_DIR}/include/openxr" + WORKING_DIRECTORY "${HPP_SOURCE_PATH}" + LOGNAME "openxr-hpp" +) + +if(VCPKG_TARGET_IS_WINDOWS) + vcpkg_cmake_config_fixup(PACKAGE_NAME OpenXR CONFIG_PATH cmake) +else() + vcpkg_cmake_config_fixup(PACKAGE_NAME OpenXR CONFIG_PATH lib/cmake/openxr) +endif() + +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share") + +vcpkg_fixup_pkgconfig() +vcpkg_copy_pdbs() +file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) diff --git a/cmake/ports/openxr-loader/python3_8_compatibility.patch b/cmake/ports/openxr-loader/python3_8_compatibility.patch new file mode 100644 index 0000000000..657bb2b7ab --- /dev/null +++ b/cmake/ports/openxr-loader/python3_8_compatibility.patch @@ -0,0 +1,13 @@ +diff --git a/scripts/hpp_genxr.py b/scripts/hpp_genxr.py +index ce419b0..23e1d3d 100644 +--- a/scripts/hpp_genxr.py ++++ b/scripts/hpp_genxr.py +@@ -36,7 +36,7 @@ from xrconventions import OpenXRConventions + from data import EXCLUDED_EXTENSIONS + + +-def makeREstring(strings: Iterable[str], default: typing.Optional[str] = None) -> str: ++def makeREstring(strings, default: typing.Optional[str] = None) -> str: + """Turn a list of strings into a regexp string matching exactly those strings.""" + if strings or default is None: + return f"^({'|'.join(re.escape(s) for s in strings)})$" diff --git a/cmake/ports/openxr-loader/vcpkg.json b/cmake/ports/openxr-loader/vcpkg.json new file mode 100644 index 0000000000..a45e3c9199 --- /dev/null +++ b/cmake/ports/openxr-loader/vcpkg.json @@ -0,0 +1,27 @@ +{ + "name": "openxr-loader", + "version": "1.0.31", + "description": "A royalty-free, open standard that provides high-performance access to Augmented Reality (AR) and Virtual Reality (VR)—collectively known as XR—platforms and devices", + "homepage": "https://github.com/KhronosGroup/OpenXR-SDK", + "license": "Apache-2.0", + "supports": "!uwp & !osx", + "dependencies": [ + "jsoncpp", + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "features": { + "vulkan": { + "description": "Vulkan functionality for OpenXR", + "dependencies": [ + "vulkan" + ] + } + } +} From 43d56eed8503fe018d651567e26a8bab517c6cec Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Mon, 26 Feb 2024 11:30:33 +0100 Subject: [PATCH 03/43] Camera: Init default values. --- libraries/shared/src/shared/Camera.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/shared/Camera.h b/libraries/shared/src/shared/Camera.h index 1c3de2b8e9..54dfea77cf 100644 --- a/libraries/shared/src/shared/Camera.h +++ b/libraries/shared/src/shared/Camera.h @@ -267,11 +267,11 @@ private: CameraMode _mode{ CAMERA_MODE_LOOK_AT }; glm::mat4 _transform; - glm::mat4 _projection; + glm::mat4 _projection = glm::mat4(1.f); // derived glm::vec3 _position { 0.0f, 0.0f, 0.0f }; - glm::quat _orientation; + glm::quat _orientation { 1.f, 0.f, 0.f, 0.f }; bool _isKeepLookingAt{ false }; glm::vec3 _lookingAt; From 8fcafa32d1acdcf9f14a84d3c3df6464fff97cac Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Sat, 2 Mar 2024 17:00:54 +0100 Subject: [PATCH 04/43] UserInputMapper: Make error an error. --- libraries/controllers/src/controllers/UserInputMapper.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 40c388ac74..e2314c56d9 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -1195,9 +1195,9 @@ Mapping::Pointer UserInputMapper::parseMapping(const QString& json) { QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8(), &error); // check validity of the document if (doc.isNull()) { - qCDebug(controllers) << "Invalid JSON...\n"; - qCDebug(controllers) << error.errorString(); - qCDebug(controllers) << "JSON was:\n" << json << Qt::endl; + qCCritical(controllers) << "Invalid JSON...\n"; + qCCritical(controllers) << error.errorString(); + qCCritical(controllers) << "JSON was:\n" << json << Qt::endl; return Mapping::Pointer(); } From f6d724241c075ea0af16a66a936a13d9f09b6b9a Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Sat, 24 Feb 2024 10:17:52 +0100 Subject: [PATCH 05/43] HmdDisplayPlugin: Fix whitespace. --- .../src/display-plugins/hmd/HmdDisplayPlugin.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 2493f07436..615827f104 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -56,16 +56,16 @@ QRect HmdDisplayPlugin::getRecommendedHUDRect() const { return CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT; } -glm::mat4 HmdDisplayPlugin::getEyeToHeadTransform(Eye eye) const { - return _eyeOffsets[eye]; +glm::mat4 HmdDisplayPlugin::getEyeToHeadTransform(Eye eye) const { + return _eyeOffsets[eye]; } -glm::mat4 HmdDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { - return _eyeProjections[eye]; +glm::mat4 HmdDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { + return _eyeProjections[eye]; } -glm::mat4 HmdDisplayPlugin::getCullingProjection(const glm::mat4& baseProjection) const { - return _cullingProjection; +glm::mat4 HmdDisplayPlugin::getCullingProjection(const glm::mat4& baseProjection) const { + return _cullingProjection; } glm::ivec4 HmdDisplayPlugin::eyeViewport(Eye eye) const { From 4b9998e53e8687297625c9260174cb6421f1bf6b Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Sun, 10 Mar 2024 21:02:57 +0100 Subject: [PATCH 06/43] Context: Maintain orientation of eye offset matrix. --- libraries/gpu/src/gpu/Backend.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/libraries/gpu/src/gpu/Backend.cpp b/libraries/gpu/src/gpu/Backend.cpp index ddda894306..2643014540 100644 --- a/libraries/gpu/src/gpu/Backend.cpp +++ b/libraries/gpu/src/gpu/Backend.cpp @@ -50,12 +50,19 @@ Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, TransformCamera result = *this; Transform eyeView = view; Transform eyePreviousView = previousView; + glm::vec3 eyePosition = extractTranslation(stereo._eyeViews[eye]); + glm::quat eyeOrientation = glmExtractRotation(stereo._eyeViews[eye]); + glm::vec3 eyePreviousPosition = extractTranslation(prevStereo._eyeViews[eye]); + glm::quat eyePreviousOrientation = glmExtractRotation(prevStereo._eyeViews[eye]); if (!stereo._skybox) { - eyeView.postTranslate(-Vec3(stereo._eyeViews[eye][3])); - eyePreviousView.postTranslate(-Vec3(prevStereo._eyeViews[eye][3])); + eyeView.postRotate(eyeOrientation).postTranslate(eyePosition); + eyePreviousView.postRotate(eyePreviousOrientation).postTranslate(eyePreviousPosition); } else { // FIXME: If "skybox" the ipd is set to 0 for now, let s try to propose a better solution for this in the future - eyePreviousView.setTranslation(vec3()); + // XRTODO: maybe this is responsible for reprojection-like stutters? + //eyePreviousView.setTranslation(vec3()); + eyeView.postRotate(eyeOrientation); + eyePreviousView.postRotate(eyePreviousOrientation); } result._projection = stereo._eyeProjections[eye]; Mat4 previousProjection = prevStereo._eyeProjections[eye]; From fff2127e660a6dcf6f269d90802499aba0855a13 Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Sun, 10 Mar 2024 21:02:42 +0100 Subject: [PATCH 07/43] Application: Maintain orientation of eye offset matrix. --- interface/src/Application_Graphics.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/interface/src/Application_Graphics.cpp b/interface/src/Application_Graphics.cpp index 9d63e661f8..f6b390a32d 100644 --- a/interface/src/Application_Graphics.cpp +++ b/interface/src/Application_Graphics.cpp @@ -551,16 +551,10 @@ void Application::updateRenderArgs(float deltaTime) { // only when the display plugin changes (or in non-HMD modes when the user // changes the FOV manually, which right now I don't think they can. for_each_eye([&](Eye eye) { - // For providing the stereo eye views, the HMD head pose has already been - // applied to the avatar, so we need to get the difference between the head - // pose applied to the avatar and the per eye pose, and use THAT as - // the per-eye stereo matrix adjustment. - mat4 eyeToHead = getActiveDisplayPlugin()->getEyeToHeadTransform(eye); // Grab the translation - vec3 eyeOffset = glm::vec3(eyeToHead[3]); + eyeOffsets[eye] = getActiveDisplayPlugin()->getEyeToHeadTransform(eye); // Apply IPD scaling - mat4 eyeOffsetTransform = glm::translate(mat4(), eyeOffset * -1.0f * ipdScale); - eyeOffsets[eye] = eyeOffsetTransform; + eyeOffsets[eye][3][0] *= ipdScale; eyeProjections[eye] = getActiveDisplayPlugin()->getEyeProjection(eye, baseProjection); }); From bb2dd63b998baa892fae481ad3ebe4c0568aa453 Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Sun, 10 Mar 2024 21:00:57 +0100 Subject: [PATCH 08/43] Application: Improve scoping. --- interface/src/Application_Graphics.cpp | 58 ++++++++++++-------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/interface/src/Application_Graphics.cpp b/interface/src/Application_Graphics.cpp index f6b390a32d..87f45e5499 100644 --- a/interface/src/Application_Graphics.cpp +++ b/interface/src/Application_Graphics.cpp @@ -522,45 +522,41 @@ void Application::updateRenderArgs(float deltaTime) { appRenderArgs._eyeToWorld = _myCamera.getTransform(); appRenderArgs._isStereo = false; - { + if (getActiveDisplayPlugin()->isStereo()) { auto hmdInterface = DependencyManager::get(); - float ipdScale = hmdInterface->getIPDScale(); - // scale IPD by sensorToWorldScale, to make the world seem larger or smaller accordingly. - ipdScale *= sensorToWorldScale; + float ipdScale = hmdInterface->getIPDScale() * sensorToWorldScale; auto baseProjection = appRenderArgs._renderArgs.getViewFrustum().getProjection(); - if (getActiveDisplayPlugin()->isStereo()) { - // Stereo modes will typically have a larger projection matrix overall, - // so we ask for the 'mono' projection matrix, which for stereo and HMD - // plugins will imply the combined projection for both eyes. - // - // This is properly implemented for the Oculus plugins, but for OpenVR - // and Stereo displays I'm not sure how to get / calculate it, so we're - // just relying on the left FOV in each case and hoping that the - // overall culling margin of error doesn't cause popping in the - // right eye. There are FIXMEs in the relevant plugins - _myCamera.setProjection(getActiveDisplayPlugin()->getCullingProjection(baseProjection)); - appRenderArgs._isStereo = true; + // Stereo modes will typically have a larger projection matrix overall, + // so we ask for the 'mono' projection matrix, which for stereo and HMD + // plugins will imply the combined projection for both eyes. + // + // This is properly implemented for the Oculus plugins, but for OpenVR + // and Stereo displays I'm not sure how to get / calculate it, so we're + // just relying on the left FOV in each case and hoping that the + // overall culling margin of error doesn't cause popping in the + // right eye. There are FIXMEs in the relevant plugins + _myCamera.setProjection(getActiveDisplayPlugin()->getCullingProjection(baseProjection)); + appRenderArgs._isStereo = true; - auto& eyeOffsets = appRenderArgs._eyeOffsets; - auto& eyeProjections = appRenderArgs._eyeProjections; + auto& eyeOffsets = appRenderArgs._eyeOffsets; + auto& eyeProjections = appRenderArgs._eyeProjections; - // FIXME we probably don't need to set the projection matrix every frame, - // only when the display plugin changes (or in non-HMD modes when the user - // changes the FOV manually, which right now I don't think they can. - for_each_eye([&](Eye eye) { - // Grab the translation - eyeOffsets[eye] = getActiveDisplayPlugin()->getEyeToHeadTransform(eye); - // Apply IPD scaling - eyeOffsets[eye][3][0] *= ipdScale; - eyeProjections[eye] = getActiveDisplayPlugin()->getEyeProjection(eye, baseProjection); - }); + // FIXME we probably don't need to set the projection matrix every frame, + // only when the display plugin changes (or in non-HMD modes when the user + // changes the FOV manually, which right now I don't think they can. + for_each_eye([&](Eye eye) { + // Grab the translation + eyeOffsets[eye] = getActiveDisplayPlugin()->getEyeToHeadTransform(eye); + // Apply IPD scaling + eyeOffsets[eye][3][0] *= ipdScale; + eyeProjections[eye] = getActiveDisplayPlugin()->getEyeProjection(eye, baseProjection); + }); - // Configure the type of display / stereo - appRenderArgs._renderArgs._displayMode = (isHMDMode() ? RenderArgs::STEREO_HMD : RenderArgs::STEREO_MONITOR); - } + // Configure the type of display / stereo + appRenderArgs._renderArgs._displayMode = (isHMDMode() ? RenderArgs::STEREO_HMD : RenderArgs::STEREO_MONITOR); } appRenderArgs._renderArgs._stencilMaskMode = getActiveDisplayPlugin()->getStencilMaskMode(); From 473bd4d64e94ad84f54dbeb3b2dcadccb6c56734 Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Mon, 12 Feb 2024 15:55:31 +0100 Subject: [PATCH 09/43] plugins: Add OpenXR plugin. Add OpenXrDisplayPlugin and OpenXrInputPlugin. Add controller bindings for the Valve Index controller. --- .../resources/controllers/openxr_index.json | 45 ++ plugins/CMakeLists.txt | 3 + plugins/openxr/CMakeLists.txt | 25 + plugins/openxr/src/OpenXrContext.cpp | 387 ++++++++++++ plugins/openxr/src/OpenXrContext.h | 82 +++ plugins/openxr/src/OpenXrDisplayPlugin.cpp | 551 ++++++++++++++++++ plugins/openxr/src/OpenXrDisplayPlugin.h | 97 +++ plugins/openxr/src/OpenXrInputPlugin.cpp | 523 +++++++++++++++++ plugins/openxr/src/OpenXrInputPlugin.h | 104 ++++ plugins/openxr/src/OpenXrProvider.cpp | 59 ++ plugins/openxr/src/plugin.json | 4 + 11 files changed, 1880 insertions(+) create mode 100644 interface/resources/controllers/openxr_index.json create mode 100644 plugins/openxr/CMakeLists.txt create mode 100644 plugins/openxr/src/OpenXrContext.cpp create mode 100644 plugins/openxr/src/OpenXrContext.h create mode 100644 plugins/openxr/src/OpenXrDisplayPlugin.cpp create mode 100644 plugins/openxr/src/OpenXrDisplayPlugin.h create mode 100644 plugins/openxr/src/OpenXrInputPlugin.cpp create mode 100644 plugins/openxr/src/OpenXrInputPlugin.h create mode 100644 plugins/openxr/src/OpenXrProvider.cpp create mode 100644 plugins/openxr/src/plugin.json diff --git a/interface/resources/controllers/openxr_index.json b/interface/resources/controllers/openxr_index.json new file mode 100644 index 0000000000..774b55981e --- /dev/null +++ b/interface/resources/controllers/openxr_index.json @@ -0,0 +1,45 @@ +{ + "name": "OpenXR Index to Standard", + "channels": [ + { "from": "Index.LeftHand", "to": "Standard.LeftHand" }, + { "from": "Index.RightHand", "to": "Standard.RightHand" }, + { "from": "Index.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] }, + + { "from": "Index.A", "to": "Standard.RightPrimaryThumb", "peek": true }, + { "from": "Index.B", "to": "Standard.RightSecondaryThumb", "peek": true }, + { "from": "Index.X", "to": "Standard.LeftPrimaryThumb", "peek": true }, + { "from": "Index.Y", "to": "Standard.LeftSecondaryThumb", "peek": true}, + + { "from": "Index.A", "to": "Standard.A" }, + { "from": "Index.B", "to": "Standard.B" }, + { "from": "Index.X", "to": "Standard.X" }, + { "from": "Index.Y", "to": "Standard.Y" }, + + { "from": "Index.LeftPrimaryThumbTouch", "to": "Standard.LeftPrimaryThumbTouch" }, + { "from": "Index.LeftSecondaryThumbTouch", "to": "Standard.LeftSecondaryThumbTouch" }, + { "from": "Index.LeftThumbUp", "to": "Standard.LeftThumbUp" }, + + { "from": "Index.RightPrimaryThumbTouch", "to": "Standard.RightPrimaryThumbTouch" }, + { "from": "Index.RightSecondaryThumbTouch", "to": "Standard.RightSecondaryThumbTouch" }, + { "from": "Index.RightThumbUp", "to": "Standard.RightThumbUp" }, + + { "from": "Index.LY", "to": "Standard.LY" }, + { "from": "Index.LX", "to": "Standard.LX" }, + { "from": "Index.RY", "to": "Standard.RY" }, + { "from": "Index.RX", "to": "Standard.RX" }, + { "from": "Index.LSTouch", "to": "Standard.LSTouch" }, + { "from": "Index.RSTouch", "to": "Standard.RSTouch" }, + + { "from": "Index.RT", "to": "Standard.RT" }, + { "from": "Index.LT", "to": "Standard.LT" }, + { "from": "Index.RTClick", "to": "Standard.RTClick" }, + { "from": "Index.LTClick", "to": "Standard.LTClick" }, + { "from": "Index.LeftPrimaryIndexTouch", "to": "Standard.LeftPrimaryIndexTouch" }, + { "from": "Index.RightPrimaryIndexTouch", "to": "Standard.RightPrimaryIndexTouch" }, + { "from": "Index.LeftIndexPoint", "to": "Standard.LeftIndexPoint" }, + { "from": "Index.RightIndexPoint", "to": "Standard.RightIndexPoint" }, + + { "from": "Index.LeftApplicationMenu", "to": "Standard.Back" }, + { "from": "Index.RightApplicationMenu", "to": "Standard.Start" } + ] +} diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index c68abefa77..5e22dfc41d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -26,6 +26,9 @@ if (NOT SERVER_ONLY AND NOT ANDROID) add_subdirectory(${DIR}) endif() + set(DIR "openxr") + add_subdirectory(${DIR}) + set(DIR "hifiSdl2") add_subdirectory(${DIR}) diff --git a/plugins/openxr/CMakeLists.txt b/plugins/openxr/CMakeLists.txt new file mode 100644 index 0000000000..2e7eb9f821 --- /dev/null +++ b/plugins/openxr/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# Copyright 2024 Lubosz Sarnecki +# +# SPDX-License-Identifier: Apache-2.0 +# + +find_package(OpenXR REQUIRED) +if (NOT OpenXR_FOUND) + MESSAGE(FATAL_ERROR "OpenXR not found!") +endif() + +set(TARGET_NAME openxr) +setup_hifi_plugin(Gui Qml Multimedia) +link_hifi_libraries(shared task gl qml networking controllers ui + plugins display-plugins ui-plugins input-plugins + audio-client render-utils graphics shaders gpu render + material-networking model-networking model-baker hfm + model-serializers ktx image procedural ${PLATFORM_GL_BACKEND} OpenXR::openxr_loader) +include_hifi_library_headers(octree) +include_hifi_library_headers(script-engine) + +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + # Silence GCC warnings + target_compile_options(openxr PRIVATE -Wno-missing-field-initializers) +endif() diff --git a/plugins/openxr/src/OpenXrContext.cpp b/plugins/openxr/src/OpenXrContext.cpp new file mode 100644 index 0000000000..700d488747 --- /dev/null +++ b/plugins/openxr/src/OpenXrContext.cpp @@ -0,0 +1,387 @@ +// +// Overte OpenXR Plugin +// +// Copyright 2024 Lubosz Sarnecki +// +// SPDX-License-Identifier: Apache-2.0 +// + +#include "OpenXrContext.h" +#include + +#include + +#include + +#define XR_USE_PLATFORM_XLIB +#define XR_USE_GRAPHICS_API_OPENGL +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(xr_context_cat) +Q_LOGGING_CATEGORY(xr_context_cat, "openxr.context") + +// Checks XrResult, returns false on errors and logs the error as qCritical. +bool xrCheck(XrInstance instance, XrResult result, const char* message) { + if (XR_SUCCEEDED(result)) + return true; + + char errorName[XR_MAX_RESULT_STRING_SIZE]; + if (instance != XR_NULL_HANDLE) { + xrResultToString(instance, result, errorName); + } else { + sprintf(errorName, "%d", result); + } + + qCCritical(xr_context_cat, "%s: %s", errorName, message); + + return false; +} + +// Extension functions must be loaded with xrGetInstanceProcAddr +static PFN_xrGetOpenGLGraphicsRequirementsKHR pfnGetOpenGLGraphicsRequirementsKHR = nullptr; +static bool initFunctionPointers(XrInstance instance) { + XrResult result = xrGetInstanceProcAddr(instance, "xrGetOpenGLGraphicsRequirementsKHR", + (PFN_xrVoidFunction*)&pfnGetOpenGLGraphicsRequirementsKHR); + return xrCheck(instance, result, "Failed to get OpenGL graphics requirements function!"); +} + +OpenXrContext::OpenXrContext() { + _isSupported = initPreGraphics(); + if (!_isSupported) { + qCCritical(xr_context_cat, "Pre graphics init failed."); + } +} + +OpenXrContext::~OpenXrContext() { + XrResult res = xrDestroyInstance(_instance); + if (res != XR_SUCCESS) { + qCCritical(xr_context_cat, "Failed to destroy OpenXR instance"); + } + qCDebug(xr_context_cat, "Destroyed instance."); +} + +bool OpenXrContext::initInstance() { + uint32_t count = 0; + XrResult result = xrEnumerateInstanceExtensionProperties(nullptr, 0, &count, nullptr); + + if (!xrCheck(XR_NULL_HANDLE, result, "Failed to enumerate number of extension properties")) + return false; + + std::vector properties; + for (uint32_t i = 0; i < count; i++) { + XrExtensionProperties props = { .type = XR_TYPE_EXTENSION_PROPERTIES }; + properties.push_back(props); + } + + result = xrEnumerateInstanceExtensionProperties(nullptr, count, &count, properties.data()); + if (!xrCheck(XR_NULL_HANDLE, result, "Failed to enumerate extension properties")) + return false; + + bool openglSupported = false; + + qCInfo(xr_context_cat, "Runtime supports %d extensions:", count); + for (uint32_t i = 0; i < count; i++) { + qCInfo(xr_context_cat, "%s v%d", properties[i].extensionName, properties[i].extensionVersion); + if (strcmp(XR_KHR_OPENGL_ENABLE_EXTENSION_NAME, properties[i].extensionName) == 0) { + openglSupported = true; + } + } + + if (!openglSupported) { + qCCritical(xr_context_cat, "Runtime does not support OpenGL!"); + return false; + } + + std::vector enabled = { XR_KHR_OPENGL_ENABLE_EXTENSION_NAME }; + + XrInstanceCreateInfo info = { + .type = XR_TYPE_INSTANCE_CREATE_INFO, + .applicationInfo = { + .applicationName = "overte", + .applicationVersion = 1, + .engineName = "overte", + .engineVersion = 0, + .apiVersion = XR_CURRENT_API_VERSION, + }, + .enabledExtensionCount = (uint32_t)enabled.size(), + .enabledExtensionNames = enabled.data(), + }; + + result = xrCreateInstance(&info, &_instance); + if (!xrCheck(XR_NULL_HANDLE, result, "Failed to create XR instance.")) + return false; + + if (!initFunctionPointers(_instance)) + return false; + + xrStringToPath(_instance, "/user/hand/left", &_handPaths[0]); + xrStringToPath(_instance, "/user/hand/right", &_handPaths[1]); + + return true; +} + +bool OpenXrContext::initSystem() { + XrSystemGetInfo info = { + .type = XR_TYPE_SYSTEM_GET_INFO, + .formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY, + }; + + XrResult result = xrGetSystem(_instance, &info, &_systemId); + if (!xrCheck(_instance, result, "Failed to get system for HMD form factor.")) + return false; + + XrSystemProperties props = { + .type = XR_TYPE_SYSTEM_PROPERTIES, + }; + + result = xrGetSystemProperties(_instance, _systemId, &props); + if (!xrCheck(_instance, result, "Failed to get System properties")) + return false; + + _systemName = QString::fromUtf8(props.systemName); + + qCInfo(xr_context_cat, "System name : %s", props.systemName); + qCInfo(xr_context_cat, "Max layers : %d", props.graphicsProperties.maxLayerCount); + qCInfo(xr_context_cat, "Max swapchain size : %dx%d", props.graphicsProperties.maxSwapchainImageHeight, + props.graphicsProperties.maxSwapchainImageWidth); + qCInfo(xr_context_cat, "Orientation Tracking: %d", props.trackingProperties.orientationTracking); + qCInfo(xr_context_cat, "Position Tracking : %d", props.trackingProperties.positionTracking); + + return true; +} + +bool OpenXrContext::initGraphics() { + XrGraphicsRequirementsOpenGLKHR requirements = { .type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR }; + XrResult result = pfnGetOpenGLGraphicsRequirementsKHR(_instance, _systemId, &requirements); + return xrCheck(_instance, result, "Failed to get OpenGL graphics requirements!"); +} + +bool OpenXrContext::requestExitSession() { + XrResult result = xrRequestExitSession(_session); + return xrCheck(_instance, result, "Failed to request exit session!"); +} + +bool OpenXrContext::initSession() { + // TODO: Make cross platform + XrGraphicsBindingOpenGLXlibKHR binding = { + .type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR, + .xDisplay = XOpenDisplay(nullptr), + .glxDrawable = glXGetCurrentDrawable(), + .glxContext = glXGetCurrentContext(), + }; + + 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"); +} + +bool OpenXrContext::initSpaces() { + // TODO: Do xrEnumerateReferenceSpaces before assuming stage space is available. + XrReferenceSpaceCreateInfo stageSpaceInfo = { + .type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO, + .referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE, + .poseInReferenceSpace = XR_INDENTITY_POSE, + }; + + XrResult result = xrCreateReferenceSpace(_session, &stageSpaceInfo, &_stageSpace); + if (!xrCheck(_instance, result, "Failed to create stage space!")) + return false; + + XrReferenceSpaceCreateInfo viewSpaceInfo = { + .type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO, + .referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW, + .poseInReferenceSpace = XR_INDENTITY_POSE, + }; + + result = xrCreateReferenceSpace(_session, &viewSpaceInfo, &_viewSpace); + return xrCheck(_instance, result, "Failed to create view space!"); +} + +#define ENUM_TO_STR(r) \ + case r: \ + return #r + +static std::string xrSessionStateStr(XrSessionState state) { + switch (state) { + ENUM_TO_STR(XR_SESSION_STATE_UNKNOWN); + ENUM_TO_STR(XR_SESSION_STATE_IDLE); + ENUM_TO_STR(XR_SESSION_STATE_READY); + ENUM_TO_STR(XR_SESSION_STATE_SYNCHRONIZED); + ENUM_TO_STR(XR_SESSION_STATE_VISIBLE); + ENUM_TO_STR(XR_SESSION_STATE_FOCUSED); + ENUM_TO_STR(XR_SESSION_STATE_STOPPING); + ENUM_TO_STR(XR_SESSION_STATE_LOSS_PENDING); + ENUM_TO_STR(XR_SESSION_STATE_EXITING); + default: { + std::ostringstream ss; + ss << "UNKNOWN STATE " << state; + return ss.str(); + } + } +} + +// Called before restarting a new session +void OpenXrContext::reset() { + _shouldQuit = false; + _lastSessionState = XR_SESSION_STATE_UNKNOWN; +} + +bool OpenXrContext::updateSessionState(XrSessionState newState) { + qCDebug(xr_context_cat, "Session state changed %s -> %s", xrSessionStateStr(_lastSessionState).c_str(), + xrSessionStateStr(newState).c_str()); + _lastSessionState = newState; + + switch (newState) { + // Don't run frame cycle but keep polling events + case XR_SESSION_STATE_IDLE: + case XR_SESSION_STATE_UNKNOWN: { + _shouldRunFrameCycle = false; + break; + } + + // Run frame cycle and poll events + case XR_SESSION_STATE_FOCUSED: + case XR_SESSION_STATE_SYNCHRONIZED: + case XR_SESSION_STATE_VISIBLE: { + _shouldRunFrameCycle = true; + break; + } + + // Begin the session + case XR_SESSION_STATE_READY: { + if (!_isSessionRunning) { + XrSessionBeginInfo session_begin_info = { + .type = XR_TYPE_SESSION_BEGIN_INFO, + .primaryViewConfigurationType = XR_VIEW_CONFIG_TYPE, + }; + XrResult result = xrBeginSession(_session, &session_begin_info); + if (!xrCheck(_instance, result, "Failed to begin session!")) + return false; + qCDebug(xr_context_cat, "Session started!"); + _isSessionRunning = true; + } + _shouldRunFrameCycle = true; + break; + } + + // End the session, don't render, but keep polling for events + case XR_SESSION_STATE_STOPPING: { + if (_isSessionRunning) { + XrResult result = xrEndSession(_session); + if (!xrCheck(_instance, result, "Failed to end session!")) + return false; + _isSessionRunning = false; + } + _shouldRunFrameCycle = false; + break; + } + + // Destroy session, skip run frame cycle, quit + case XR_SESSION_STATE_LOSS_PENDING: + case XR_SESSION_STATE_EXITING: { + XrResult result = xrDestroySession(_session); + if (!xrCheck(_instance, result, "Failed to destroy session!")) + return false; + _shouldQuit = true; + _shouldRunFrameCycle = false; + qCDebug(xr_context_cat, "Destroyed session"); + break; + } + default: + qCWarning(xr_context_cat, "Unhandled session state: %d", newState); + } + + return true; +} + +bool OpenXrContext::pollEvents() { + XrEventDataBuffer event = { .type = XR_TYPE_EVENT_DATA_BUFFER }; + XrResult result = xrPollEvent(_instance, &event); + while (result == XR_SUCCESS) { + switch (event.type) { + case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { + XrEventDataInstanceLossPending* instanceLossPending = (XrEventDataInstanceLossPending*)&event; + qCCritical(xr_context_cat, "Instance loss pending at %lu! Destroying instance.", instanceLossPending->lossTime); + _shouldQuit = true; + continue; + } + case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { + XrEventDataSessionStateChanged* sessionStateChanged = (XrEventDataSessionStateChanged*)&event; + if (!updateSessionState(sessionStateChanged->state)) { + return false; + } + break; + } + case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { + for (int i = 0; i < HAND_COUNT; i++) { + XrInteractionProfileState state = { .type = XR_TYPE_INTERACTION_PROFILE_STATE }; + XrResult res = xrGetCurrentInteractionProfile(_session, _handPaths[i], &state); + if (!xrCheck(_instance, res, "Failed to get interaction profile")) + continue; + + uint32_t bufferCountOutput; + char profilePath[XR_MAX_PATH_LENGTH]; + res = xrPathToString(_instance, state.interactionProfile, XR_MAX_PATH_LENGTH, &bufferCountOutput, + profilePath); + if (!xrCheck(_instance, res, "Failed to get interaction profile path.")) + continue; + + qCInfo(xr_context_cat, "Controller %d: Interaction profile changed to '%s'", i, profilePath); + } + break; + } + default: + qCWarning(xr_context_cat, "Unhandled event type %d", event.type); + } + + event.type = XR_TYPE_EVENT_DATA_BUFFER; + result = xrPollEvent(_instance, &event); + } + + if (result != XR_EVENT_UNAVAILABLE) { + qCCritical(xr_context_cat, "Failed to poll events!"); + return false; + } + + return true; +} + +bool OpenXrContext::beginFrame() { + XrFrameBeginInfo info = { .type = XR_TYPE_FRAME_BEGIN_INFO }; + XrResult result = xrBeginFrame(_session, &info); + return xrCheck(_instance, result, "failed to begin frame!"); +} + +bool OpenXrContext::initPreGraphics() { + if (!initInstance()) { + return false; + } + + if (!initSystem()) { + return false; + } + + return true; +} + +bool OpenXrContext::initPostGraphics() { + if (!initGraphics()) { + return false; + } + + if (!initSession()) { + return false; + } + + if (!initSpaces()) { + return false; + } + + return true; +} diff --git a/plugins/openxr/src/OpenXrContext.h b/plugins/openxr/src/OpenXrContext.h new file mode 100644 index 0000000000..387bab388e --- /dev/null +++ b/plugins/openxr/src/OpenXrContext.h @@ -0,0 +1,82 @@ +// +// Overte OpenXR Plugin +// +// Copyright 2024 Lubosz Sarnecki +// +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include "controllers/Pose.h" + +#include + +#include +#include + +#define HAND_COUNT 2 + +constexpr XrPosef XR_INDENTITY_POSE = { + .orientation = { .x = 0, .y = 0, .z = 0, .w = 1.0 }, + .position = { .x = 0, .y = 0, .z = 0 }, +}; + +constexpr XrViewConfigurationType XR_VIEW_CONFIG_TYPE = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + +class OpenXrContext { +public: + XrInstance _instance = XR_NULL_HANDLE; + XrSession _session = XR_NULL_HANDLE; + XrSystemId _systemId = XR_NULL_SYSTEM_ID; + + XrSpace _stageSpace = XR_NULL_HANDLE; + XrSpace _viewSpace = XR_NULL_HANDLE; + XrPath _handPaths[HAND_COUNT]; + + controller::Pose _lastHeadPose; + XrTime _lastPredictedDisplayTime; + // TODO: Enable C++17 and use std::optional + bool _lastPredictedDisplayTimeInitialized = false; + + bool _shouldQuit = false; + bool _shouldRunFrameCycle = false; + + bool _isSupported = false; + + QString _systemName; + bool _isSessionRunning = false; + +private: + XrSessionState _lastSessionState = XR_SESSION_STATE_UNKNOWN; + +public: + OpenXrContext(); + ~OpenXrContext(); + + bool initPostGraphics(); + bool beginFrame(); + bool pollEvents(); + bool requestExitSession(); + void reset(); + +private: + bool initPreGraphics(); + bool initInstance(); + bool initSystem(); + bool initGraphics(); + bool initSession(); + bool initSpaces(); + + bool updateSessionState(XrSessionState newState); +}; + +inline static glm::vec3 xrVecToGlm(const XrVector3f& v) { + return glm::vec3(v.x, v.y, v.z); +} + +inline static glm::quat xrQuatToGlm(const XrQuaternionf& q) { + return glm::quat(q.w, q.x, q.y, q.z); +} + +bool xrCheck(XrInstance instance, XrResult result, const char* message); \ No newline at end of file diff --git a/plugins/openxr/src/OpenXrDisplayPlugin.cpp b/plugins/openxr/src/OpenXrDisplayPlugin.cpp new file mode 100644 index 0000000000..e952fe671c --- /dev/null +++ b/plugins/openxr/src/OpenXrDisplayPlugin.cpp @@ -0,0 +1,551 @@ +// +// Overte OpenXR Plugin +// +// Copyright 2024 Lubosz Sarnecki +// +// SPDX-License-Identifier: Apache-2.0 +// + +#include "OpenXrDisplayPlugin.h" +#include + +#include "ViewFrustum.h" + +#include +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(xr_display_cat) +Q_LOGGING_CATEGORY(xr_display_cat, "openxr.display") + +constexpr GLint XR_PREFERRED_COLOR_FORMAT = GL_SRGB8_ALPHA8; + +OpenXrDisplayPlugin::OpenXrDisplayPlugin(std::shared_ptr c) { + _context = c; +} + +bool OpenXrDisplayPlugin::isSupported() const { + return _context->_isSupported; +} + +// Slightly differs from glm::ortho +inline static glm::mat4 fovToProjection(const XrFovf fov, const float near, const float far) { + const float left = tanf(fov.angleLeft); + const float right = tanf(fov.angleRight); + const float down = tanf(fov.angleDown); + const float up = tanf(fov.angleUp); + + const float width = right - left; + const float height = up - down; + + const float m11 = 2 / width; + const float m22 = 2 / height; + const float m33 = -(far + near) / (far - near); + + const float m31 = (right + left) / width; + const float m32 = (up + down) / height; + const float m43 = -(far * (near + near)) / (far - near); + + // clang-format off + const float mat[16] = { + m11, 0 , 0 , 0, + 0 , m22, 0 , 0, + m31, m32, m33, -1, + 0 , 0 , m43, 0, + }; + // clang-format on + + return glm::make_mat4(mat); +} + +glm::mat4 OpenXrDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { + if (!_viewsInitialized) { + return baseProjection; + } + + ViewFrustum frustum; + frustum.setProjection(baseProjection); + return fovToProjection(_views[(eye == Left) ? 0 : 1].fov, frustum.getNearClip(), frustum.getFarClip()); +} + +// TODO: This apparently wasn't right in the OpenVR plugin, but this is what it basically did. +glm::mat4 OpenXrDisplayPlugin::getCullingProjection(const glm::mat4& baseProjection) const { + return getEyeProjection(Left, baseProjection); +} + +// TODO: This should not be explicilty known by the application. +// Let's just render as fast as we can and OpenXR will dictate the pace. +float OpenXrDisplayPlugin::getTargetFrameRate() const { + return std::numeric_limits::max(); +} + +bool OpenXrDisplayPlugin::initViews() { + XrInstance instance = _context->_instance; + XrSystemId systemId = _context->_systemId; + + XrResult result = xrEnumerateViewConfigurationViews(instance, systemId, XR_VIEW_CONFIG_TYPE, 0, &_viewCount, nullptr); + if (!xrCheck(instance, result, "Failed to get view configuration view count!")) { + qCCritical(xr_display_cat, "Failed to get view configuration view count!"); + return false; + } + + assert(_viewCount != 0); + + for (uint32_t i = 0; i < _viewCount; i++) { + XrView view = { .type = XR_TYPE_VIEW }; + _views.push_back(view); + + XrViewConfigurationView viewConfig = { .type = XR_TYPE_VIEW_CONFIGURATION_VIEW }; + _viewConfigs.push_back(viewConfig); + } + + _swapChains.resize(_viewCount); + _swapChainLengths.resize(_viewCount); + _swapChainIndices.resize(_viewCount); + _images.resize(_viewCount); + + result = xrEnumerateViewConfigurationViews(instance, systemId, XR_VIEW_CONFIG_TYPE, _viewCount, &_viewCount, + _viewConfigs.data()); + if (!xrCheck(instance, result, "Failed to enumerate view configuration views!")) { + qCCritical(xr_display_cat, "Failed to enumerate view configuration views!"); + return false; + } + + return true; +} + +#define ENUM_TO_STR(r) \ + case r: \ + return #r + +static std::string glFormatStr(GLenum source) { + switch (source) { + ENUM_TO_STR(GL_RGBA16); + ENUM_TO_STR(GL_RGBA16F); + ENUM_TO_STR(GL_SRGB8_ALPHA8); + default: { + // TODO: Enable C++20 for std::format + std::ostringstream ss; + ss << "0x" << std::hex << source; + return ss.str(); + } + } +} + +static int64_t chooseSwapChainFormat(XrInstance instance, XrSession session, int64_t preferred) { + uint32_t formatCount; + XrResult result = xrEnumerateSwapchainFormats(session, 0, &formatCount, nullptr); + if (!xrCheck(instance, result, "Failed to get number of supported swapchain formats")) + return -1; + + qCInfo(xr_display_cat, "Runtime supports %d swapchain formats", formatCount); + std::vector formats(formatCount); + + result = xrEnumerateSwapchainFormats(session, formatCount, &formatCount, formats.data()); + if (!xrCheck(instance, result, "Failed to enumerate swapchain formats")) + return -1; + + int64_t chosen = formats[0]; + + for (uint32_t i = 0; i < formatCount; i++) { + qCInfo(xr_display_cat, "Supported GL format: %s", glFormatStr(formats[i]).c_str()); + if (formats[i] == preferred) { + chosen = formats[i]; + qCInfo(xr_display_cat, "Using preferred swapchain format %s", glFormatStr(chosen).c_str()); + break; + } + } + if (chosen != preferred) { + qCWarning(xr_display_cat, "Falling back to non preferred swapchain format %s", glFormatStr(chosen).c_str()); + } + + return chosen; +} + +bool OpenXrDisplayPlugin::initSwapChains() { + XrInstance instance = _context->_instance; + XrSession session = _context->_session; + + int64_t format = chooseSwapChainFormat(instance, session, XR_PREFERRED_COLOR_FORMAT); + + for (uint32_t i = 0; i < _viewCount; i++) { + _images[i].clear(); + + XrSwapchainCreateInfo info = { + .type = XR_TYPE_SWAPCHAIN_CREATE_INFO, + .createFlags = 0, + .usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, + .format = format, + .sampleCount = _viewConfigs[i].recommendedSwapchainSampleCount, + .width = _viewConfigs[i].recommendedImageRectWidth, + .height = _viewConfigs[i].recommendedImageRectHeight, + .faceCount = 1, + .arraySize = 1, + .mipCount = 1, + }; + + XrResult result = xrCreateSwapchain(session, &info, &_swapChains[i]); + if (!xrCheck(instance, result, "Failed to create swapchain!")) + return false; + + result = xrEnumerateSwapchainImages(_swapChains[i], 0, &_swapChainLengths[i], nullptr); + if (!xrCheck(instance, result, "Failed to enumerate swapchains")) + return false; + + for (uint32_t j = 0; j < _swapChainLengths[i]; j++) { + XrSwapchainImageOpenGLKHR image = { .type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR }; + _images[i].push_back(image); + } + result = xrEnumerateSwapchainImages(_swapChains[i], _swapChainLengths[i], &_swapChainLengths[i], + (XrSwapchainImageBaseHeader*)_images[i].data()); + if (!xrCheck(instance, result, "Failed to enumerate swapchain images")) + return false; + } + + return true; +} + +bool OpenXrDisplayPlugin::initLayers() { + for (uint32_t i = 0; i < _viewCount; i++) { + XrCompositionLayerProjectionView layer = { + .type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW, + .subImage = { + .swapchain = _swapChains[i], + .imageRect = { + .offset = { + .x = 0, + .y = 0, + }, + .extent = { + .width = (int32_t)_viewConfigs[i].recommendedImageRectWidth, + .height = (int32_t)_viewConfigs[i].recommendedImageRectHeight, + }, + }, + .imageArrayIndex = 0, + }, + }; + _projectionLayerViews.push_back(layer); + }; + + return true; +} + +void OpenXrDisplayPlugin::init() { + Plugin::init(); + + if (!initViews()) { + qCCritical(xr_display_cat, "View init failed."); + return; + } + + for (const XrViewConfigurationView& view : _viewConfigs) { + assert(view.recommendedImageRectWidth != 0); + qCDebug(xr_display_cat, "Swapchain dimensions: %dx%d", view.recommendedImageRectWidth, view.recommendedImageRectHeight); + // TODO: Don't render side-by-side but use multiview (texture arrays). This probably won't work with GL. + _renderTargetSize.x = view.recommendedImageRectWidth * 2; + _renderTargetSize.y = view.recommendedImageRectHeight; + } + + emit deviceConnected(getName()); +} + +const QString OpenXrDisplayPlugin::getName() const { + return QString("OpenXR: %1").arg(_context->_systemName); +} + +bool OpenXrDisplayPlugin::internalActivate() { + _context->reset(); + return HmdDisplayPlugin::internalActivate(); +} + +void OpenXrDisplayPlugin::internalDeactivate() { + // We can get into a state where activate -> deactivate -> activate is called in a chain. + // We are probably gonna have a bad time then. At least check if the session is already running. + // This happens when the application decides to switch display plugins back and forth. This should + // prbably be fixed there. + if (_context->_isSessionRunning) { + if (!_context->requestExitSession()) { + qCCritical(xr_display_cat, "Failed to request exit session"); + } else { + // Poll events until runtime wants to quit + while (!_context->_shouldQuit) { + _context->pollEvents(); + } + } + } + HmdDisplayPlugin::internalDeactivate(); +} + +void OpenXrDisplayPlugin::customizeContext() { + gl::initModuleGl(); + HmdDisplayPlugin::customizeContext(); + + if (!_context->initPostGraphics()) { + qCCritical(xr_display_cat, "Post graphics init failed."); + return; + } + + if (!initSwapChains()) { + qCCritical(xr_display_cat, "Swap chain init failed."); + return; + } + + if (!initLayers()) { + qCCritical(xr_display_cat, "Layer init failed."); + return; + } + + // Create swap chain images for _compositeFramebuffer + for (size_t i = 0; i < _swapChainLengths[0]; ++i) { + gpu::TexturePointer texture = + gpu::Texture::createRenderBuffer(gpu::Element::COLOR_SRGBA_32, _renderTargetSize.x, _renderTargetSize.y, + gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT)); + _compositeSwapChain.push_back(texture); + } +} + +void OpenXrDisplayPlugin::uncustomizeContext() { + _compositeSwapChain.clear(); + _projectionLayerViews.clear(); + for (uint32_t i = 0; i < _viewCount; i++) { + _images[i].clear(); + } + HmdDisplayPlugin::uncustomizeContext(); +} + +void OpenXrDisplayPlugin::resetSensors() { +} + +bool OpenXrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { + _context->pollEvents(); + + if (_context->_shouldQuit) { + QMetaObject::invokeMethod(qApp, "quit"); + return false; + } + + if (!_context->_shouldRunFrameCycle) { + qCWarning(xr_display_cat, "beginFrameRender: Shoudln't run frame cycle. Skipping renderin frame %d", 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; + } + } + + _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; + _context->_lastPredictedDisplayTimeInitialized = true; + + 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.data()); + if (!xrCheck(_context->_instance, result, "Could not locate views")) + return false; + + for (uint32_t i = 0; i < _viewCount; i++) { + _projectionLayerViews[i].pose = _views[i].pose; + _projectionLayerViews[i].fov = _views[i].fov; + } + + _viewsInitialized = true; + + 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; + + return HmdDisplayPlugin::beginFrameRender(frameIndex); +} + +void OpenXrDisplayPlugin::submitFrame(const gpu::FramePointer& newFrame) { + OpenGLDisplayPlugin::submitFrame(newFrame); + { + std::unique_lock lock(_haveFrameMutex); + _haveFrameToSubmit = true; + } +} + +void OpenXrDisplayPlugin::compositeLayers() { + if (!_context->_shouldRunFrameCycle) { + return; + } + + if (_lastFrameState.shouldRender) { + _compositeFramebuffer->setRenderBuffer(0, _compositeSwapChain[_swapChainIndices[0]]); + HmdDisplayPlugin::compositeLayers(); + } +} + +void OpenXrDisplayPlugin::hmdPresent() { + if (!_context->_shouldRunFrameCycle) { + qCWarning(xr_display_cat, "hmdPresent: Shoudln't run frame cycle. Skipping renderin frame %d", + _currentFrame->frameIndex); + return; + } + + if (_lastFrameState.shouldRender) { + // TODO: Use multiview swapchain + for (uint32_t i = 0; i < 2; i++) { + XrSwapchainImageAcquireInfo acquireInfo = { .type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO }; + + XrResult result = xrAcquireSwapchainImage(_swapChains[i], &acquireInfo, &_swapChainIndices[i]); + if (!xrCheck(_context->_instance, result, "failed to acquire swapchain image!")) + return; + + XrSwapchainImageWaitInfo waitInfo = { .type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, .timeout = 1000 }; + result = xrWaitSwapchainImage(_swapChains[i], &waitInfo); + if (!xrCheck(_context->_instance, result, "failed to wait for swapchain image!")) + return; + } + + GLuint glTexId = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0)); + + glCopyImageSubData(glTexId, GL_TEXTURE_2D, 0, 0, 0, 0, _images[0][_swapChainIndices[0]].image, GL_TEXTURE_2D, 0, 0, 0, + 0, _renderTargetSize.x / 2, _renderTargetSize.y, 1); + + glCopyImageSubData(glTexId, GL_TEXTURE_2D, 0, _renderTargetSize.x / 2, 0, 0, _images[1][_swapChainIndices[1]].image, + GL_TEXTURE_2D, 0, 0, 0, 0, _renderTargetSize.x / 2, _renderTargetSize.y, 1); + + for (uint32_t i = 0; i < 2; i++) { + XrSwapchainImageReleaseInfo releaseInfo = { .type = XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO }; + XrResult result = xrReleaseSwapchainImage(_swapChains[i], &releaseInfo); + if (!xrCheck(_context->_instance, result, "failed to release swapchain image!")) { + assert(false); + return; + } + } + } + + endFrame(); + + _presentRate.increment(); + + { + std::unique_lock lock(_haveFrameMutex); + _haveFrameToSubmit = false; + } +} + +bool OpenXrDisplayPlugin::endFrame() { + XrCompositionLayerProjection projectionLayer = { + .type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, + .layerFlags = 0, + .space = _context->_stageSpace, + .viewCount = _viewCount, + .views = _projectionLayerViews.data(), + }; + + std::vector layers = { + (const XrCompositionLayerBaseHeader*)&projectionLayer, + }; + + XrFrameEndInfo info = { + .type = XR_TYPE_FRAME_END_INFO, + .displayTime = _lastFrameState.predictedDisplayTime, + .environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE, + .layerCount = (uint32_t)layers.size(), + .layers = layers.data(), + }; + + if ((_lastViewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) == 0) { + qCWarning(xr_display_cat, "Not submitting layers because orientation is invalid."); + info.layerCount = 0; + } + + if (!_lastFrameState.shouldRender) { + info.layerCount = 0; + } + + XrResult result = xrEndFrame(_context->_session, &info); + if (!xrCheck(_context->_instance, result, "failed to end frame!")) { + return false; + } + + return true; +} + +void OpenXrDisplayPlugin::postPreview() { +} + +bool OpenXrDisplayPlugin::isHmdMounted() const { + return true; +} + +void OpenXrDisplayPlugin::updatePresentPose() { +} + +int OpenXrDisplayPlugin::getRequiredThreadCount() const { + return HmdDisplayPlugin::getRequiredThreadCount(); +} + +QRectF OpenXrDisplayPlugin::getPlayAreaRect() { + return QRectF(0, 0, 10, 10); +} + +DisplayPlugin::StencilMaskMeshOperator OpenXrDisplayPlugin::getStencilMaskMeshOperator() { + return nullptr; +} diff --git a/plugins/openxr/src/OpenXrDisplayPlugin.h b/plugins/openxr/src/OpenXrDisplayPlugin.h new file mode 100644 index 0000000000..e36dba44f5 --- /dev/null +++ b/plugins/openxr/src/OpenXrDisplayPlugin.h @@ -0,0 +1,97 @@ +// +// Overte OpenXR Plugin +// +// Copyright 2024 Lubosz Sarnecki +// +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include + +#include "OpenXrContext.h" + +#include "gpu/gl/GLBackend.h" + +#include + +#define XR_USE_PLATFORM_XLIB +#define XR_USE_GRAPHICS_API_OPENGL +#include +#include + +class OpenXrDisplayPlugin : public HmdDisplayPlugin { +public: + OpenXrDisplayPlugin(std::shared_ptr c); + bool isSupported() const override; + const QString getName() const override; + bool getSupportsAutoSwitch() override final { return true; } + + glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override; + glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override; + + void init() override; + + float getTargetFrameRate() const override; + bool hasAsyncReprojection() const override { return true; } + + void customizeContext() override; + void uncustomizeContext() override; + + void resetSensors() override; + bool beginFrameRender(uint32_t frameIndex) override; + void submitFrame(const gpu::FramePointer& newFrame) override; + void cycleDebugOutput() override { _lockCurrentTexture = !_lockCurrentTexture; } + + int getRequiredThreadCount() const override; + + QRectF getPlayAreaRect() override; + + virtual StencilMaskMode getStencilMaskMode() const override { return StencilMaskMode::MESH; } + virtual StencilMaskMeshOperator getStencilMaskMeshOperator() override; + + glm::mat4 getSensorResetMatrix() const { return glm::mat4(1.0f); } + +protected: + bool internalActivate() override; + void internalDeactivate() override; + void updatePresentPose() override; + + void compositeLayers() override; + void hmdPresent() override; + bool isHmdMounted() const override; + void postPreview() override; + +private: + std::vector _compositeSwapChain; + + XrViewState _lastViewState; + + std::shared_ptr _context; + + uint32_t _viewCount = 0; + std::vector _projectionLayerViews; + + std::vector _views; + // TODO: Enable C++17 and use std::optional + bool _viewsInitialized = false; + + std::vector _viewConfigs; + + std::vector _swapChains; + std::vector _swapChainLengths; + std::vector _swapChainIndices; + std::vector> _images; + + XrFrameState _lastFrameState; + + bool initViews(); + bool initSwapChains(); + bool initLayers(); + bool endFrame(); + + bool _haveFrameToSubmit = false; + std::mutex _haveFrameMutex; +}; diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp new file mode 100644 index 0000000000..f5eaabff0b --- /dev/null +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -0,0 +1,523 @@ +// +// Overte OpenXR Plugin +// +// Copyright 2024 Lubosz Sarnecki +// +// SPDX-License-Identifier: Apache-2.0 +// + +#include "OpenXrInputPlugin.h" + +#include "AvatarConstants.h" +#include "PathUtils.h" + +#include "controllers/UserInputMapper.h" + +Q_DECLARE_LOGGING_CATEGORY(xr_input_cat) +Q_LOGGING_CATEGORY(xr_input_cat, "openxr.input") + +OpenXrInputPlugin::OpenXrInputPlugin(std::shared_ptr c) { + _context = c; + _inputDevice = std::make_shared(_context); +} + +// TODO: Make a config UI +static const QString XR_CONFIGURATION_LAYOUT = QString(""); + +void OpenXrInputPlugin::calibrate() { +} + +bool OpenXrInputPlugin::uncalibrate() { + return true; +} + +bool OpenXrInputPlugin::isSupported() const { + return _context->_isSupported; +} + +void OpenXrInputPlugin::setConfigurationSettings(const QJsonObject configurationSettings) { +} + +QJsonObject OpenXrInputPlugin::configurationSettings() { + return QJsonObject(); +} + +QString OpenXrInputPlugin::configurationLayout() { + return XR_CONFIGURATION_LAYOUT; +} + +bool OpenXrInputPlugin::activate() { + InputPlugin::activate(); + + loadSettings(); + + // register with UserInputMapper + auto userInputMapper = DependencyManager::get(); + userInputMapper->registerDevice(_inputDevice); + _registeredWithInputMapper = true; + + return true; +} + +void OpenXrInputPlugin::deactivate() { + InputPlugin::deactivate(); + + _inputDevice->_poseStateMap.clear(); + + // unregister with UserInputMapper + auto userInputMapper = DependencyManager::get(); + userInputMapper->removeDevice(_inputDevice->_deviceID); + _registeredWithInputMapper = false; + + saveSettings(); +} + +void OpenXrInputPlugin::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + if (_context->_shouldQuit) { + deactivate(); + return; + } + + auto userInputMapper = DependencyManager::get(); + userInputMapper->withLock([&, this]() { _inputDevice->update(deltaTime, inputCalibrationData); }); + + if (_inputDevice->_trackedControllers == 0 && _registeredWithInputMapper) { + userInputMapper->removeDevice(_inputDevice->_deviceID); + _registeredWithInputMapper = false; + _inputDevice->_poseStateMap.clear(); + } + + if (!_registeredWithInputMapper && _inputDevice->_trackedControllers > 0) { + userInputMapper->registerDevice(_inputDevice); + _registeredWithInputMapper = true; + } +} + +void OpenXrInputPlugin::loadSettings() { +} + +void OpenXrInputPlugin::saveSettings() const { +} + +OpenXrInputPlugin::InputDevice::InputDevice(std::shared_ptr c) : controller::InputDevice("Index") { + _context = c; +} + +void OpenXrInputPlugin::InputDevice::focusOutEvent() { + _axisStateMap.clear(); + _buttonPressedMap.clear(); +}; + +bool OpenXrInputPlugin::InputDevice::triggerHapticPulse(float strength, float duration, uint16_t index) { + if (index > 2) { + return false; + } + + std::unique_lock locker(_lock); + + // TODO: convert duration and strength to openxr values. + if (!_actions.at("/output/haptic")->applyHaptic(0, XR_MIN_HAPTIC_DURATION, XR_FREQUENCY_UNSPECIFIED, 0.5f)) { + qCCritical(xr_input_cat, "Failed to apply haptic feedback!"); + } + + return true; +} + +bool OpenXrInputPlugin::Action::init(XrActionSet actionSet) { + XrInstance instance = _context->_instance; + XrActionCreateInfo info = { + .type = XR_TYPE_ACTION_CREATE_INFO, + .actionType = _type, + .countSubactionPaths = HAND_COUNT, + .subactionPaths = _context->_handPaths, + }; + + QString name = QString::fromStdString(_path); + name.replace("/input/", ""); + name.replace("/", "-"); + strcpy(info.actionName, name.toUtf8().data()); + name.replace("-", " "); + strcpy(info.localizedActionName, name.toUtf8().data()); + + XrResult result = xrCreateAction(actionSet, &info, &_action); + if (!xrCheck(instance, result, "Failed to create action")) + return false; + + // Pose actions need spaces + if (_type == XR_ACTION_TYPE_POSE_INPUT) { + if (!createPoseSpaces()) { + return false; + } + } + + return true; +} + +const std::vector HAND_PATHS = { "left", "right" }; + +std::vector OpenXrInputPlugin::Action::getBindings() { + assert(_action != XR_NULL_HANDLE); + + std::vector bindings; + for (uint32_t i = 0; i < HAND_COUNT; i++) { + XrPath path; + std::string pathString = "/user/hand/" + HAND_PATHS[i] + _path; + xrStringToPath(_context->_instance, pathString.c_str(), &path); + XrActionSuggestedBinding binding = { .action = _action, .binding = path }; + bindings.push_back(binding); + } + return bindings; +} + +XrActionStateFloat OpenXrInputPlugin::Action::getFloat(uint32_t handId) { + XrActionStateFloat state = { + .type = XR_TYPE_ACTION_STATE_FLOAT, + }; + + XrActionStateGetInfo info = { + .type = XR_TYPE_ACTION_STATE_GET_INFO, + .action = _action, + .subactionPath = _context->_handPaths[handId], + }; + + XrResult result = xrGetActionStateFloat(_context->_session, &info, &state); + xrCheck(_context->_instance, result, "Failed to get float state!"); + + return state; +} + +XrActionStateBoolean OpenXrInputPlugin::Action::getBool(uint32_t handId) { + XrActionStateBoolean state = { + .type = XR_TYPE_ACTION_STATE_BOOLEAN, + }; + + XrActionStateGetInfo info = { + .type = XR_TYPE_ACTION_STATE_GET_INFO, + .action = _action, + .subactionPath = _context->_handPaths[handId], + }; + + XrResult result = xrGetActionStateBoolean(_context->_session, &info, &state); + xrCheck(_context->_instance, result, "Failed to get float state!"); + + return state; +} + +XrSpaceLocation OpenXrInputPlugin::Action::getPose(uint32_t handId) { + XrActionStatePose state = { + .type = XR_TYPE_ACTION_STATE_POSE, + }; + XrActionStateGetInfo info = { + .type = XR_TYPE_ACTION_STATE_GET_INFO, + .action = _action, + .subactionPath = _context->_handPaths[handId], + }; + + XrResult result = xrGetActionStatePose(_context->_session, &info, &state); + xrCheck(_context->_instance, result, "failed to get pose value!"); + + XrSpaceLocation location = { + .type = XR_TYPE_SPACE_LOCATION, + }; + + if (_context->_lastPredictedDisplayTimeInitialized) { + result = xrLocateSpace(_poseSpaces[handId], _context->_stageSpace, _context->_lastPredictedDisplayTime, &location); + xrCheck(_context->_instance, result, "Failed to locate hand space!"); + } + + return location; +} + +bool OpenXrInputPlugin::Action::applyHaptic(uint32_t handId, XrDuration duration, float frequency, float amplitude) { + XrHapticVibration vibration = { + .type = XR_TYPE_HAPTIC_VIBRATION, + .duration = duration, + .frequency = frequency, + .amplitude = amplitude, + }; + + XrHapticActionInfo haptic_action_info = { + .type = XR_TYPE_HAPTIC_ACTION_INFO, + .action = _action, + .subactionPath = _context->_handPaths[handId], + }; + XrResult result = xrApplyHapticFeedback(_context->_session, &haptic_action_info, (const XrHapticBaseHeader*)&vibration); + + return xrCheck(_context->_instance, result, "Failed to apply haptic feedback!"); +} + +bool OpenXrInputPlugin::Action::createPoseSpaces() { + assert(_action != XR_NULL_HANDLE); + + for (int hand = 0; hand < HAND_COUNT; hand++) { + XrActionSpaceCreateInfo info = { + .type = XR_TYPE_ACTION_SPACE_CREATE_INFO, + .action = _action, + .subactionPath = _context->_handPaths[hand], + .poseInActionSpace = XR_INDENTITY_POSE, + }; + + XrResult result = xrCreateActionSpace(_context->_session, &info, &_poseSpaces[hand]); + if (!xrCheck(_context->_instance, result, "Failed to create hand pose space")) + return false; + } + + return true; +} + +bool OpenXrInputPlugin::InputDevice::initBindings(const std::string& profileName, + const std::vector& actionsToBind) { + XrPath profilePath; + XrResult result = xrStringToPath(_context->_instance, profileName.c_str(), &profilePath); + if (!xrCheck(_context->_instance, result, "Failed to get interaction profile")) + return false; + + std::vector bindings; + for (const std::string& path : actionsToBind) { + std::vector actionBindings = _actions.at(path)->getBindings(); + bindings.insert(std::end(bindings), std::begin(actionBindings), std::end(actionBindings)); + } + + const XrInteractionProfileSuggestedBinding suggestedBinding = { + .type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING, + .interactionProfile = profilePath, + .countSuggestedBindings = (uint32_t)bindings.size(), + .suggestedBindings = bindings.data(), + }; + + result = xrSuggestInteractionProfileBindings(_context->_instance, &suggestedBinding); + + return xrCheck(_context->_instance, result, "Failed to suggest bindings"); +} + +controller::Input::NamedVector OpenXrInputPlugin::InputDevice::getAvailableInputs() const { + using namespace controller; + + // clang-format off + QVector availableInputs{ + // Poses + makePair(LEFT_HAND, "LeftHand"), + makePair(RIGHT_HAND, "RightHand"), + makePair(HEAD, "Head"), + // Sticks + makePair(LX, "LX"), + makePair(LY, "LY"), + makePair(RX, "RX"), + makePair(RY, "RY"), + // Face buttons + makePair(A, "A"), + makePair(B, "B"), + makePair(X, "X"), + makePair(Y, "Y"), + // Triggers + makePair(RT, "RT"), + makePair(LT, "LT"), + makePair(RT_CLICK, "RTClick"), + makePair(LT_CLICK, "LTClick"), + // Menu buttons + // TODO: Add this to button channel + // Input::NamedPair(Input(_deviceID, LEFT_APP_MENU, ChannelType::BUTTON), "LeftApplicationMenu"), + // Input::NamedPair(Input(_deviceID, RIGHT_APP_MENU, ChannelType::BUTTON), "RightApplicationMenu"), + }; + // clang-format on + + return availableInputs; +} + +QString OpenXrInputPlugin::InputDevice::getDefaultMappingConfig() const { + return PathUtils::resourcesPath() + "/controllers/openxr_index.json"; +} + +bool OpenXrInputPlugin::InputDevice::initActions() { + if (_actionsInitialized) + return true; + + assert(_context->_session != XR_NULL_HANDLE); + + XrInstance instance = _context->_instance; + + XrActionSetCreateInfo actionSetInfo = { + .type = XR_TYPE_ACTION_SET_CREATE_INFO, + .actionSetName = "action_set", + .localizedActionSetName = "Action Set", + .priority = 0, + }; + XrResult result = xrCreateActionSet(instance, &actionSetInfo, &_actionSet); + if (!xrCheck(instance, result, "Failed to create action set.")) + return false; + + // clang-format off + std::map actionsToInit = { + { "/input/thumbstick/x", XR_ACTION_TYPE_FLOAT_INPUT }, + { "/input/thumbstick/y", XR_ACTION_TYPE_FLOAT_INPUT }, + { "/input/a/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/b/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/trigger/value", XR_ACTION_TYPE_FLOAT_INPUT }, + { "/input/trigger/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/output/haptic", XR_ACTION_TYPE_VIBRATION_OUTPUT }, + { "/input/grip/pose", XR_ACTION_TYPE_POSE_INPUT }, + { "/input/select/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/system/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + }; + // clang-format on + + for (const auto& pathAndType : actionsToInit) { + std::shared_ptr action = std::make_shared(_context, pathAndType.second, pathAndType.first); + if (!action->init(_actionSet)) { + qCCritical(xr_input_cat, "Creating action %s failed!", pathAndType.first.c_str()); + } else { + _actions.emplace(pathAndType.first, action); + } + } + + // Khronos Simple Controller + std::vector simpleBindings = { + "/input/grip/pose", + "/input/select/click", + "/output/haptic", + }; + + if (!initBindings("/interaction_profiles/khr/simple_controller", simpleBindings)) { + qCCritical(xr_input_cat, "Failed to init bindings."); + } + + // Valve Index Controller + // clang-format off + std::vector indexBindings = { + "/input/grip/pose", + "/input/thumbstick/x", + "/input/thumbstick/y", + "/input/a/click", + "/input/b/click", + "/input/trigger/value", + "/output/haptic", + "/input/system/click", + }; + // clang-format on + + if (!initBindings("/interaction_profiles/valve/index_controller", indexBindings)) { + qCCritical(xr_input_cat, "Failed to init bindings."); + } + + XrSessionActionSetsAttachInfo attachInfo = { + .type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO, + .countActionSets = 1, + .actionSets = &_actionSet, + }; + result = xrAttachSessionActionSets(_context->_session, &attachInfo); + if (!xrCheck(_context->_instance, result, "Failed to attach action set")) + return false; + + _actionsInitialized = true; + + return true; +} + +void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + _poseStateMap.clear(); + _buttonPressedMap.clear(); + _trackedControllers = 2; + + if (_context->_session == XR_NULL_HANDLE) { + return; + } + + if (!initActions()) { + qCCritical(xr_input_cat, "Could not initialize actions!"); + return; + } + + const XrActiveActionSet active_actionset = { + .actionSet = _actionSet, + }; + + XrActionsSyncInfo syncInfo = { + .type = XR_TYPE_ACTIONS_SYNC_INFO, + .countActiveActionSets = 1, + .activeActionSets = &active_actionset, + }; + + XrInstance instance = _context->_instance; + XrSession session = _context->_session; + + XrResult result = xrSyncActions(session, &syncInfo); + xrCheck(instance, result, "failed to sync actions!"); + + glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; + + static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); + static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); + static const glm::quat touchToHand = yFlip * quarterX; + + static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X); + + static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ * eighthX) * touchToHand; + static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand; + + for (int i = 0; i < HAND_COUNT; i++) { + XrSpaceLocation handLocation = _actions.at("/input/grip/pose")->getPose(i); + bool locationValid = (handLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0; + if (locationValid) { + vec3 translation = xrVecToGlm(handLocation.pose.position); + quat rotation = xrQuatToGlm(handLocation.pose.orientation); + auto pose = controller::Pose(translation, rotation); + glm::mat4 handOffset = i == 0 ? glm::toMat4(leftRotationOffset) : glm::toMat4(rightRotationOffset); + _poseStateMap[i == 0 ? controller::LEFT_HAND : controller::RIGHT_HAND] = + pose.postTransform(handOffset).transform(sensorToAvatar); + } + } + + glm::mat4 defaultHeadOffset = createMatFromQuatAndPos(-DEFAULT_AVATAR_HEAD_ROT, -DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET); + _poseStateMap[controller::HEAD] = _context->_lastHeadPose.postTransform(defaultHeadOffset).transform(sensorToAvatar); + + std::map axesToUpdate[2] = { + { + { controller::LX, "/input/thumbstick/x" }, + { controller::LY, "/input/thumbstick/y" }, + { controller::LT, "/input/trigger/value" }, + }, + { + { controller::RX, "/input/thumbstick/x" }, + { controller::RY, "/input/thumbstick/y" }, + { controller::RT, "/input/trigger/value" }, + }, + }; + + for (uint32_t i = 0; i < HAND_COUNT; i++) { + for (const auto& channelAndPath : axesToUpdate[i]) { + _axisStateMap[channelAndPath.first].value = _actions.at(channelAndPath.second)->getFloat(i).currentState; + + // if (_axisStateMap[channelAndPath.first].value != 0) { + // qCDebug(xr_input_cat, "🐸 Controller %d: %s (%d): %f", i, channelAndPath.second.c_str(), channelAndPath.first, + // (double)_axisStateMap[channelAndPath.first].value); + // } + } + } + + // TODO: Figure out why LEFT_APP_MENU is misssing in StandardButtonChannel + std::map buttonsToUpdate[2] = { + { + { controller::X, "/input/a/click" }, + { controller::Y, "/input/b/click" }, + { controller::LT_CLICK, "/input/trigger/click" }, + //{ LEFT_APP_MENU, "/input/system/click" }, + }, + { + { controller::A, "/input/a/click" }, + { controller::B, "/input/b/click" }, + { controller::RT_CLICK, "/input/trigger/click" }, + //{ RIGHT_APP_MENU, "/input/system/click" }, + }, + }; + + for (uint32_t i = 0; i < HAND_COUNT; i++) { + for (const auto& channelAndPath : buttonsToUpdate[i]) { + if (_actions.at(channelAndPath.second)->getBool(i).currentState == XR_TRUE) { + _buttonPressedMap.insert(channelAndPath.first); + // qCDebug(xr_input_cat, "🐸 Controller %d: %s (%d)", i, channelAndPath.second.c_str(), channelAndPath.first); + } + } + } +} \ No newline at end of file diff --git a/plugins/openxr/src/OpenXrInputPlugin.h b/plugins/openxr/src/OpenXrInputPlugin.h new file mode 100644 index 0000000000..f1a578867d --- /dev/null +++ b/plugins/openxr/src/OpenXrInputPlugin.h @@ -0,0 +1,104 @@ +// +// Overte OpenXR Plugin +// +// Copyright 2024 Lubosz Sarnecki +// +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include "plugins/InputPlugin.h" +#include "controllers/InputDevice.h" +#include "OpenXrContext.h" + +#define HAND_COUNT 2 + +class OpenXrInputPlugin : public InputPlugin { + Q_OBJECT +public: + OpenXrInputPlugin(std::shared_ptr c); + bool isSupported() const override; + const QString getName() const override { return "OpenXR"; } + + bool isHandController() const override { return true; } + bool configurable() override { return true; } + + QString configurationLayout() override; + void setConfigurationSettings(const QJsonObject configurationSettings) override; + QJsonObject configurationSettings() override; + void calibrate() override; + bool uncalibrate() override; + bool isHeadController() const override { return true; } + + bool activate() override; + void deactivate() override; + + QString getDeviceName() override { return _context.get()->_systemName; } + + void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } + + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + + virtual void saveSettings() const override; + virtual void loadSettings() override; + +private: + class Action { + public: + Action(std::shared_ptr c, XrActionType type, const std::string& path) { + _context = c; + _path = path; + _type = type; + } + + bool init(XrActionSet actionSet); + std::vector getBindings(); + XrActionStateFloat getFloat(uint32_t handId); + XrActionStateBoolean getBool(uint32_t handId); + XrSpaceLocation getPose(uint32_t handId); + bool applyHaptic(uint32_t handId, XrDuration duration, float frequency, float amplitude); + + private: + bool createPoseSpaces(); + XrAction _action = XR_NULL_HANDLE; + std::shared_ptr _context; + std::string _path; + XrActionType _type; + XrSpace _poseSpaces[HAND_COUNT] = { XR_NULL_HANDLE, XR_NULL_HANDLE }; + }; + + class InputDevice : public controller::InputDevice { + public: + InputDevice(std::shared_ptr c); + + private: + controller::Input::NamedVector getAvailableInputs() const override; + QString getDefaultMappingConfig() const override; + void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + void focusOutEvent() override; + bool triggerHapticPulse(float strength, float duration, uint16_t index) override; + + mutable std::recursive_mutex _lock; + template + void withLock(F&& f) { + std::unique_lock locker(_lock); + f(); + } + + friend class OpenXrInputPlugin; + + uint32_t _trackedControllers = 0; + XrActionSet _actionSet; + std::map> _actions; + std::shared_ptr _context; + bool _actionsInitialized = false; + + bool initActions(); + bool initBindings(const std::string& profileName, const std::vector& actionsToBind); + }; + + bool _registeredWithInputMapper = false; + std::shared_ptr _context; + std::shared_ptr _inputDevice; +}; diff --git a/plugins/openxr/src/OpenXrProvider.cpp b/plugins/openxr/src/OpenXrProvider.cpp new file mode 100644 index 0000000000..c2a87dff67 --- /dev/null +++ b/plugins/openxr/src/OpenXrProvider.cpp @@ -0,0 +1,59 @@ +// +// Overte OpenXR Plugin +// +// Copyright 2024 Lubosz Sarnecki +// +// SPDX-License-Identifier: Apache-2.0 +// + +#include "plugins/RuntimePlugin.h" +#include "OpenXrDisplayPlugin.h" +#include "OpenXrInputPlugin.h" + +class OpenXrProvider : public QObject, public DisplayProvider, InputProvider { + Q_OBJECT + Q_PLUGIN_METADATA(IID DisplayProvider_iid FILE "plugin.json") + Q_INTERFACES(DisplayProvider) + Q_PLUGIN_METADATA(IID InputProvider_iid FILE "plugin.json") + Q_INTERFACES(InputProvider) + +public: + OpenXrProvider(QObject* parent = nullptr) : QObject(parent) {} + virtual ~OpenXrProvider() {} + std::shared_ptr context = std::make_shared(); + + virtual DisplayPluginList getDisplayPlugins() override { + static std::once_flag once; + std::call_once(once, [&] { + DisplayPluginPointer plugin(std::make_shared(context)); + if (plugin->isSupported()) { + _displayPlugins.push_back(plugin); + } + }); + + return _displayPlugins; + } + + virtual InputPluginList getInputPlugins() override { + static std::once_flag once; + + std::call_once(once, [&] { + InputPluginPointer plugin(std::make_shared(context)); + if (plugin->isSupported()) { + _inputPlugins.push_back(plugin); + } + }); + + return _inputPlugins; + } + + virtual void destroyInputPlugins() override { _inputPlugins.clear(); } + + virtual void destroyDisplayPlugins() override { _displayPlugins.clear(); } + +private: + DisplayPluginList _displayPlugins; + InputPluginList _inputPlugins; +}; + +#include "OpenXrProvider.moc" diff --git a/plugins/openxr/src/plugin.json b/plugins/openxr/src/plugin.json new file mode 100644 index 0000000000..5a3df6e736 --- /dev/null +++ b/plugins/openxr/src/plugin.json @@ -0,0 +1,4 @@ +{ + "name":"OpenXR", + "version":1 +} From be3e12b18c210eab1a636f95cb1170d4e0248960 Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Thu, 14 Mar 2024 16:31:20 +0100 Subject: [PATCH 10/43] OpenXrContext: Improve errors when runtime is not available. --- plugins/openxr/src/OpenXrContext.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/plugins/openxr/src/OpenXrContext.cpp b/plugins/openxr/src/OpenXrContext.cpp index 700d488747..9b50057362 100644 --- a/plugins/openxr/src/OpenXrContext.cpp +++ b/plugins/openxr/src/OpenXrContext.cpp @@ -49,11 +49,14 @@ static bool initFunctionPointers(XrInstance instance) { OpenXrContext::OpenXrContext() { _isSupported = initPreGraphics(); if (!_isSupported) { - qCCritical(xr_context_cat, "Pre graphics init failed."); + qCWarning(xr_context_cat, "OpenXR is not supported."); } } OpenXrContext::~OpenXrContext() { + if (_instance == XR_NULL_HANDLE) { + return; + } XrResult res = xrDestroyInstance(_instance); if (res != XR_SUCCESS) { qCCritical(xr_context_cat, "Failed to destroy OpenXR instance"); @@ -65,7 +68,13 @@ bool OpenXrContext::initInstance() { uint32_t count = 0; XrResult result = xrEnumerateInstanceExtensionProperties(nullptr, 0, &count, nullptr); - if (!xrCheck(XR_NULL_HANDLE, result, "Failed to enumerate number of extension properties")) + // Since this is the first OpenXR call we do, check here if RUNTIME_UNAVAILABLE is returned. + if (result == XR_ERROR_RUNTIME_UNAVAILABLE) { + qCCritical(xr_context_cat, "XR_ERROR_RUNTIME_UNAVAILABLE: Is XR_RUNTIME_JSON set correctly?"); + return false; + } + + if (!xrCheck(XR_NULL_HANDLE, result, "Failed to enumerate number of extensions.")) return false; std::vector properties; @@ -75,7 +84,7 @@ bool OpenXrContext::initInstance() { } result = xrEnumerateInstanceExtensionProperties(nullptr, count, &count, properties.data()); - if (!xrCheck(XR_NULL_HANDLE, result, "Failed to enumerate extension properties")) + if (!xrCheck(XR_NULL_HANDLE, result, "Failed to enumerate extensions.")) return false; bool openglSupported = false; @@ -109,7 +118,13 @@ bool OpenXrContext::initInstance() { }; result = xrCreateInstance(&info, &_instance); - if (!xrCheck(XR_NULL_HANDLE, result, "Failed to create XR instance.")) + + if (result == XR_ERROR_RUNTIME_FAILURE) { + qCCritical(xr_context_cat, "XR_ERROR_RUNTIME_FAILURE: Is the OpenXR runtime up and running?"); + return false; + } + + if (!xrCheck(XR_NULL_HANDLE, result, "Failed to create OpenXR instance.")) return false; if (!initFunctionPointers(_instance)) From d9cd2fd3d5f34d61b6003657db3a10dec5c53846 Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Sat, 16 Mar 2024 15:55:42 +0100 Subject: [PATCH 11/43] OpenGlDisplayPlugin: Add possibility to present frames only once. Don't present frame more than once in OpenXrDisplayPlugin. --- .../src/display-plugins/OpenGLDisplayPlugin.cpp | 7 +++++++ .../src/display-plugins/OpenGLDisplayPlugin.h | 2 ++ plugins/openxr/src/OpenXrDisplayPlugin.cpp | 1 + 3 files changed, 10 insertions(+) diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index d5ce52de18..2943ff465b 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -745,6 +745,13 @@ 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 4dc10a7aa1..2048c1498f 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -205,6 +205,8 @@ protected: QImage getScreenshot(float aspectRatio); QImage getSecondaryCameraScreenshot(); + bool _presentOnlyOnce = false; + private: static Setting::Handle _extraLinearToSRGBConversionSetting; }; diff --git a/plugins/openxr/src/OpenXrDisplayPlugin.cpp b/plugins/openxr/src/OpenXrDisplayPlugin.cpp index e952fe671c..e15de872dd 100644 --- a/plugins/openxr/src/OpenXrDisplayPlugin.cpp +++ b/plugins/openxr/src/OpenXrDisplayPlugin.cpp @@ -23,6 +23,7 @@ constexpr GLint XR_PREFERRED_COLOR_FORMAT = GL_SRGB8_ALPHA8; OpenXrDisplayPlugin::OpenXrDisplayPlugin(std::shared_ptr c) { _context = c; + _presentOnlyOnce = true; } bool OpenXrDisplayPlugin::isSupported() const { From 2db31686122690ffde839143f1c4f3bf9f2992ae Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Wed, 13 Mar 2024 01:41:18 +0100 Subject: [PATCH 12/43] OpenXrInput: Improve mapping for the Index controller. * Add thumbstick click and touch. * Add face and trigger touch. Use left primary / secondary instead of face button names. --- .../resources/controllers/openxr_index.json | 15 +++--- plugins/openxr/src/OpenXrInputPlugin.cpp | 48 +++++++++++++++---- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/interface/resources/controllers/openxr_index.json b/interface/resources/controllers/openxr_index.json index 774b55981e..15ee5b5d3b 100644 --- a/interface/resources/controllers/openxr_index.json +++ b/interface/resources/controllers/openxr_index.json @@ -5,15 +5,10 @@ { "from": "Index.RightHand", "to": "Standard.RightHand" }, { "from": "Index.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] }, - { "from": "Index.A", "to": "Standard.RightPrimaryThumb", "peek": true }, - { "from": "Index.B", "to": "Standard.RightSecondaryThumb", "peek": true }, - { "from": "Index.X", "to": "Standard.LeftPrimaryThumb", "peek": true }, - { "from": "Index.Y", "to": "Standard.LeftSecondaryThumb", "peek": true}, - - { "from": "Index.A", "to": "Standard.A" }, - { "from": "Index.B", "to": "Standard.B" }, - { "from": "Index.X", "to": "Standard.X" }, - { "from": "Index.Y", "to": "Standard.Y" }, + { "from": "Index.RightPrimaryThumb", "to": "Standard.RightPrimaryThumb" }, + { "from": "Index.RightSecondaryThumb", "to": "Standard.RightSecondaryThumb" }, + { "from": "Index.LeftPrimaryThumb", "to": "Standard.LeftPrimaryThumb" }, + { "from": "Index.LeftSecondaryThumb", "to": "Standard.LeftSecondaryThumb" }, { "from": "Index.LeftPrimaryThumbTouch", "to": "Standard.LeftPrimaryThumbTouch" }, { "from": "Index.LeftSecondaryThumbTouch", "to": "Standard.LeftSecondaryThumbTouch" }, @@ -27,6 +22,8 @@ { "from": "Index.LX", "to": "Standard.LX" }, { "from": "Index.RY", "to": "Standard.RY" }, { "from": "Index.RX", "to": "Standard.RX" }, + { "from": "Index.LS", "to": "Standard.LS" }, + { "from": "Index.RS", "to": "Standard.RS" }, { "from": "Index.LSTouch", "to": "Standard.LSTouch" }, { "from": "Index.RSTouch", "to": "Standard.RSTouch" }, diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index f5eaabff0b..58ab880814 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -302,18 +302,29 @@ controller::Input::NamedVector OpenXrInputPlugin::InputDevice::getAvailableInput // Sticks makePair(LX, "LX"), makePair(LY, "LY"), + makePair(LS, "LS"), + makePair(LS_TOUCH, "LSTouch"), makePair(RX, "RX"), makePair(RY, "RY"), + makePair(RS, "RS"), + makePair(RS_TOUCH, "RSTouch"), // Face buttons - makePair(A, "A"), - makePair(B, "B"), - makePair(X, "X"), - makePair(Y, "Y"), + makePair(RIGHT_PRIMARY_THUMB, "RightPrimaryThumb"), + makePair(RIGHT_PRIMARY_THUMB_TOUCH, "RightPrimaryThumbTouch"), + makePair(RIGHT_SECONDARY_THUMB, "RightSecondaryThumb"), + makePair(RIGHT_SECONDARY_THUMB_TOUCH, "RightSecondaryThumbTouch"), + + makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"), + makePair(LEFT_PRIMARY_THUMB_TOUCH, "LeftPrimaryThumbTouch"), + makePair(LEFT_SECONDARY_THUMB, "LeftSecondaryThumb"), + makePair(LEFT_SECONDARY_THUMB_TOUCH, "LeftSecondaryThumbTouch"), // Triggers makePair(RT, "RT"), makePair(LT, "LT"), makePair(RT_CLICK, "RTClick"), makePair(LT_CLICK, "LTClick"), + makePair(LEFT_PRIMARY_INDEX_TOUCH, "LeftPrimaryIndexTouch"), + makePair(RIGHT_PRIMARY_INDEX_TOUCH, "RightPrimaryIndexTouch"), // Menu buttons // TODO: Add this to button channel // Input::NamedPair(Input(_deviceID, LEFT_APP_MENU, ChannelType::BUTTON), "LeftApplicationMenu"), @@ -350,10 +361,15 @@ bool OpenXrInputPlugin::InputDevice::initActions() { std::map actionsToInit = { { "/input/thumbstick/x", XR_ACTION_TYPE_FLOAT_INPUT }, { "/input/thumbstick/y", XR_ACTION_TYPE_FLOAT_INPUT }, + { "/input/thumbstick/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/thumbstick/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, { "/input/a/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/a/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, { "/input/b/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/b/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, { "/input/trigger/value", XR_ACTION_TYPE_FLOAT_INPUT }, { "/input/trigger/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/trigger/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, { "/output/haptic", XR_ACTION_TYPE_VIBRATION_OUTPUT }, { "/input/grip/pose", XR_ACTION_TYPE_POSE_INPUT }, { "/input/select/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, @@ -387,9 +403,15 @@ bool OpenXrInputPlugin::InputDevice::initActions() { "/input/grip/pose", "/input/thumbstick/x", "/input/thumbstick/y", + "/input/thumbstick/touch", + "/input/thumbstick/click", "/input/a/click", + "/input/a/touch", "/input/b/click", + "/input/b/touch", "/input/trigger/value", + "/input/trigger/click", + "/input/trigger/touch", "/output/haptic", "/input/system/click", }; @@ -499,15 +521,25 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I // TODO: Figure out why LEFT_APP_MENU is misssing in StandardButtonChannel std::map buttonsToUpdate[2] = { { - { controller::X, "/input/a/click" }, - { controller::Y, "/input/b/click" }, + { controller::LEFT_PRIMARY_THUMB, "/input/a/click" }, + { controller::LEFT_PRIMARY_THUMB_TOUCH, "/input/a/touch" }, + { controller::LEFT_SECONDARY_THUMB, "/input/b/click" }, + { controller::LEFT_SECONDARY_THUMB_TOUCH, "/input/b/touch" }, { controller::LT_CLICK, "/input/trigger/click" }, + { controller::LEFT_PRIMARY_INDEX_TOUCH, "/input/trigger/touch" }, + { controller::LS, "/input/thumbstick/click" }, + { controller::LS_TOUCH, "/input/thumbstick/touch" }, //{ LEFT_APP_MENU, "/input/system/click" }, }, { - { controller::A, "/input/a/click" }, - { controller::B, "/input/b/click" }, + { controller::RIGHT_PRIMARY_THUMB, "/input/a/click" }, + { controller::RIGHT_PRIMARY_THUMB_TOUCH, "/input/a/touch" }, + { controller::RIGHT_SECONDARY_THUMB, "/input/b/click" }, + { controller::RIGHT_SECONDARY_THUMB_TOUCH, "/input/b/touch" }, { controller::RT_CLICK, "/input/trigger/click" }, + { controller::RIGHT_PRIMARY_INDEX_TOUCH, "/input/trigger/touch" }, + { controller::RS, "/input/thumbstick/click" }, + { controller::RS_TOUCH, "/input/thumbstick/touch" }, //{ RIGHT_APP_MENU, "/input/system/click" }, }, }; From 0505658a39e0601820294e230887c156a7dbadbc Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Sun, 17 Mar 2024 10:39:41 +0100 Subject: [PATCH 13/43] controllers: openxr: Use actions for walking. This makes the Y thumbstick axis work. --- interface/resources/controllers/openxr_index.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/controllers/openxr_index.json b/interface/resources/controllers/openxr_index.json index 15ee5b5d3b..7a1199e1a0 100644 --- a/interface/resources/controllers/openxr_index.json +++ b/interface/resources/controllers/openxr_index.json @@ -18,8 +18,8 @@ { "from": "Index.RightSecondaryThumbTouch", "to": "Standard.RightSecondaryThumbTouch" }, { "from": "Index.RightThumbUp", "to": "Standard.RightThumbUp" }, - { "from": "Index.LY", "to": "Standard.LY" }, - { "from": "Index.LX", "to": "Standard.LX" }, + { "from": "Index.LY", "to": "Actions.TranslateZ", "filters": ["invert"] }, + { "from": "Index.LX", "to": "Actions.TranslateX" }, { "from": "Index.RY", "to": "Standard.RY" }, { "from": "Index.RX", "to": "Standard.RX" }, { "from": "Index.LS", "to": "Standard.LS" }, From 7e31a1e49b865193b22be5f9158a147e87cf11ce Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Sun, 17 Mar 2024 11:09:52 +0100 Subject: [PATCH 14/43] controllers: Improve openxr_index.json mapping. --- .../resources/controllers/openxr_index.json | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/interface/resources/controllers/openxr_index.json b/interface/resources/controllers/openxr_index.json index 7a1199e1a0..44e1ae0a97 100644 --- a/interface/resources/controllers/openxr_index.json +++ b/interface/resources/controllers/openxr_index.json @@ -1,29 +1,26 @@ { - "name": "OpenXR Index to Standard", + "name": "OpenXR Index to Actions", "channels": [ { "from": "Index.LeftHand", "to": "Standard.LeftHand" }, { "from": "Index.RightHand", "to": "Standard.RightHand" }, { "from": "Index.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] }, - { "from": "Index.RightPrimaryThumb", "to": "Standard.RightPrimaryThumb" }, - { "from": "Index.RightSecondaryThumb", "to": "Standard.RightSecondaryThumb" }, - { "from": "Index.LeftPrimaryThumb", "to": "Standard.LeftPrimaryThumb" }, - { "from": "Index.LeftSecondaryThumb", "to": "Standard.LeftSecondaryThumb" }, - + { "from": "Index.LeftPrimaryThumb", "to": "Actions.Down" }, { "from": "Index.LeftPrimaryThumbTouch", "to": "Standard.LeftPrimaryThumbTouch" }, + { "from": "Index.LeftSecondaryThumb", "to": "Actions.ContextMenu" }, { "from": "Index.LeftSecondaryThumbTouch", "to": "Standard.LeftSecondaryThumbTouch" }, - { "from": "Index.LeftThumbUp", "to": "Standard.LeftThumbUp" }, + { "from": "Index.RightPrimaryThumb", "to": "Actions.Up" }, { "from": "Index.RightPrimaryThumbTouch", "to": "Standard.RightPrimaryThumbTouch" }, + { "from": "Index.RightSecondaryThumb", "to": "Actions.Sprint" }, { "from": "Index.RightSecondaryThumbTouch", "to": "Standard.RightSecondaryThumbTouch" }, - { "from": "Index.RightThumbUp", "to": "Standard.RightThumbUp" }, { "from": "Index.LY", "to": "Actions.TranslateZ", "filters": ["invert"] }, { "from": "Index.LX", "to": "Actions.TranslateX" }, { "from": "Index.RY", "to": "Standard.RY" }, { "from": "Index.RX", "to": "Standard.RX" }, { "from": "Index.LS", "to": "Standard.LS" }, - { "from": "Index.RS", "to": "Standard.RS" }, + { "from": "Index.RS", "to": "Actions.CycleCamera" }, { "from": "Index.LSTouch", "to": "Standard.LSTouch" }, { "from": "Index.RSTouch", "to": "Standard.RSTouch" }, @@ -31,12 +28,8 @@ { "from": "Index.LT", "to": "Standard.LT" }, { "from": "Index.RTClick", "to": "Standard.RTClick" }, { "from": "Index.LTClick", "to": "Standard.LTClick" }, - { "from": "Index.LeftPrimaryIndexTouch", "to": "Standard.LeftPrimaryIndexTouch" }, - { "from": "Index.RightPrimaryIndexTouch", "to": "Standard.RightPrimaryIndexTouch" }, - { "from": "Index.LeftIndexPoint", "to": "Standard.LeftIndexPoint" }, - { "from": "Index.RightIndexPoint", "to": "Standard.RightIndexPoint" }, - { "from": "Index.LeftApplicationMenu", "to": "Standard.Back" }, - { "from": "Index.RightApplicationMenu", "to": "Standard.Start" } + { "from": "Index.LeftPrimaryIndexTouch", "to": "Standard.LeftPrimaryIndexTouch" }, + { "from": "Index.RightPrimaryIndexTouch", "to": "Standard.RightPrimaryIndexTouch" } ] } From 89ba66c317ba0ebfbcc1ed7e1eb0b8b96f8b0440 Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Sun, 17 Mar 2024 14:10:00 +0100 Subject: [PATCH 15/43] OpenXrInputPlugin: Improve haptic feedback mapping. --- plugins/openxr/src/OpenXrInputPlugin.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index 58ab880814..04582ae92f 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -115,8 +115,15 @@ bool OpenXrInputPlugin::InputDevice::triggerHapticPulse(float strength, float du std::unique_lock locker(_lock); - // TODO: convert duration and strength to openxr values. - if (!_actions.at("/output/haptic")->applyHaptic(0, XR_MIN_HAPTIC_DURATION, XR_FREQUENCY_UNSPECIFIED, 0.5f)) { + // TODO: Haptic values in overte are always strengh 1.0 and duration only 13.0 or 16.0. So it's not really used. + // The duration does not seem to map to a time unit. 16ms seems quite short for a haptic vibration. + // Let's assume the duration is in 10 milliseconds. + // Let's also assume strength 1.0 is the middle value, which is 0.5 in OpenXR. + using namespace std::chrono; + nanoseconds durationNs = duration_cast(milliseconds(static_cast(duration * 10.0f))); + XrDuration xrDuration = durationNs.count(); + + if (!_actions.at("/output/haptic")->applyHaptic(index, xrDuration, XR_FREQUENCY_UNSPECIFIED, 0.5f * strength)) { qCCritical(xr_input_cat, "Failed to apply haptic feedback!"); } From 7abc7be28727cd9b1bf803d08d3d7c899db56815 Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Sun, 17 Mar 2024 22:43:40 +0100 Subject: [PATCH 16/43] OpenXrContext: Add Windows platform and bindings. --- plugins/openxr/src/OpenXrContext.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/plugins/openxr/src/OpenXrContext.cpp b/plugins/openxr/src/OpenXrContext.cpp index 9b50057362..127ab7ae9a 100644 --- a/plugins/openxr/src/OpenXrContext.cpp +++ b/plugins/openxr/src/OpenXrContext.cpp @@ -11,9 +11,15 @@ #include -#include +#if defined(Q_OS_LINUX) + #include + #define XR_USE_PLATFORM_XLIB +#elif defined(Q_OS_WIN) + #define XR_USE_PLATFORM_WIN32 +#else + #error "Unsupported platform" +#endif -#define XR_USE_PLATFORM_XLIB #define XR_USE_GRAPHICS_API_OPENGL #include #include @@ -178,14 +184,22 @@ bool OpenXrContext::requestExitSession() { } bool OpenXrContext::initSession() { - // TODO: Make cross platform +#if defined(Q_OS_LINUX) XrGraphicsBindingOpenGLXlibKHR binding = { .type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR, .xDisplay = XOpenDisplay(nullptr), .glxDrawable = glXGetCurrentDrawable(), .glxContext = glXGetCurrentContext(), }; - +#elif defined(Q_OS_WIN) + XrGraphicsBindingOpenGLWin32KHR binding = { + .type = XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR, + .hDC = wglGetCurrentDC(), + .hGLRC = wglGetCurrentContext(), + }; +#else + #error "Unsupported platform" +#endif XrSessionCreateInfo info = { .type = XR_TYPE_SESSION_CREATE_INFO, .next = &binding, From 05c6a5e5d78f767215cb21c1d6b57dd7b0a9acd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Sun, 7 Jul 2024 15:55:20 +0200 Subject: [PATCH 17/43] OpenXr: Enable C++20 for OpenXR plugin. Enabling it globally causes issue with WebRTC. Co-authored-by: Lubosz Sarnecki --- plugins/openxr/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/openxr/CMakeLists.txt b/plugins/openxr/CMakeLists.txt index 2e7eb9f821..9316a77f63 100644 --- a/plugins/openxr/CMakeLists.txt +++ b/plugins/openxr/CMakeLists.txt @@ -23,3 +23,5 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") # Silence GCC warnings target_compile_options(openxr PRIVATE -Wno-missing-field-initializers) endif() + +set_property(TARGET openxr PROPERTY CXX_STANDARD 20) \ No newline at end of file From 661fe08f080d90ffc24830960d50df4bee4efe3a Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Tue, 3 Sep 2024 23:20:56 +0200 Subject: [PATCH 18/43] GLMHelpers: Fix build with C++20 on GCC. When enabling C++20 the lerp function seems to be redefined on GCC (not on MSVC), don't redefine it using a CMake definition. --- libraries/shared/src/GLMHelpers.h | 2 ++ plugins/openxr/CMakeLists.txt | 3 +++ 2 files changed, 5 insertions(+) diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index cfb4bb6398..c74820e4a0 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -212,9 +212,11 @@ T toNormalizedDeviceScale(const T& value, const T& size) { #define ROLL(euler) euler.z // float - linear interpolate +#if !defined(DONT_REDEFINE_LERP) inline float lerp(float x, float y, float a) { return x * (1.0f - a) + (y * a); } +#endif // vec2 lerp - linear interpolate template diff --git a/plugins/openxr/CMakeLists.txt b/plugins/openxr/CMakeLists.txt index 9316a77f63..c54d5efcc2 100644 --- a/plugins/openxr/CMakeLists.txt +++ b/plugins/openxr/CMakeLists.txt @@ -22,6 +22,9 @@ include_hifi_library_headers(script-engine) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") # Silence GCC warnings target_compile_options(openxr PRIVATE -Wno-missing-field-initializers) + + # Fix build issue where lerp is already defined on C++20 / GCC + target_compile_definitions(openxr PRIVATE -DDONT_REDEFINE_LERP) endif() set_property(TARGET openxr PROPERTY CXX_STANDARD 20) \ No newline at end of file From c8557f5c78efa54b5c147275cd3dbc4a24057fb6 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Fri, 9 Aug 2024 20:12:12 -0700 Subject: [PATCH 19/43] OpenXr: Platform improvements. Add missing includes on Windows. Include GLX only on Linux. Move openxr_platform.h include to OpenXrContext.h. To make this possible, certain names from GLX/X11 need to be undefined in order to be now includable in OpenXrInput.h. Add Overte e.V. copyright. Co-authored-by: Lubosz Sarnecki --- plugins/openxr/CMakeLists.txt | 1 + plugins/openxr/src/OpenXrContext.cpp | 14 +----------- plugins/openxr/src/OpenXrContext.h | 25 +++++++++++++++++++++- plugins/openxr/src/OpenXrDisplayPlugin.cpp | 8 ++++++- plugins/openxr/src/OpenXrDisplayPlugin.h | 10 +-------- plugins/openxr/src/OpenXrInputPlugin.cpp | 1 + plugins/openxr/src/OpenXrInputPlugin.h | 1 + plugins/openxr/src/OpenXrProvider.cpp | 1 + 8 files changed, 37 insertions(+), 24 deletions(-) diff --git a/plugins/openxr/CMakeLists.txt b/plugins/openxr/CMakeLists.txt index c54d5efcc2..847a41880e 100644 --- a/plugins/openxr/CMakeLists.txt +++ b/plugins/openxr/CMakeLists.txt @@ -1,5 +1,6 @@ # # Copyright 2024 Lubosz Sarnecki +# Copyright 2024 Overte e.V. # # SPDX-License-Identifier: Apache-2.0 # diff --git a/plugins/openxr/src/OpenXrContext.cpp b/plugins/openxr/src/OpenXrContext.cpp index 127ab7ae9a..2664b49bcf 100644 --- a/plugins/openxr/src/OpenXrContext.cpp +++ b/plugins/openxr/src/OpenXrContext.cpp @@ -2,6 +2,7 @@ // Overte OpenXR Plugin // // Copyright 2024 Lubosz Sarnecki +// Copyright 2024 Overte e.V. // // SPDX-License-Identifier: Apache-2.0 // @@ -11,19 +12,6 @@ #include -#if defined(Q_OS_LINUX) - #include - #define XR_USE_PLATFORM_XLIB -#elif defined(Q_OS_WIN) - #define XR_USE_PLATFORM_WIN32 -#else - #error "Unsupported platform" -#endif - -#define XR_USE_GRAPHICS_API_OPENGL -#include -#include - Q_DECLARE_LOGGING_CATEGORY(xr_context_cat) Q_LOGGING_CATEGORY(xr_context_cat, "openxr.context") diff --git a/plugins/openxr/src/OpenXrContext.h b/plugins/openxr/src/OpenXrContext.h index 387bab388e..eed18eeeb8 100644 --- a/plugins/openxr/src/OpenXrContext.h +++ b/plugins/openxr/src/OpenXrContext.h @@ -2,19 +2,42 @@ // Overte OpenXR Plugin // // Copyright 2024 Lubosz Sarnecki +// Copyright 2024 Overte e.V. // // SPDX-License-Identifier: Apache-2.0 // #pragma once -#include "controllers/Pose.h" #include +#include "gpu/gl/GLBackend.h" + +#if defined(Q_OS_LINUX) + #define XR_USE_PLATFORM_XLIB + #include + // Unsorted from glx.h conflicts with qdir.h + #undef Unsorted + // MappingPointer from X11 conflicts with one from controllers/Forward.h + #undef MappingPointer +#elif defined(Q_OS_WIN) + #define XR_USE_PLATFORM_WIN32 + #include + #include +#else + #error "Unimplemented platform" +#endif + + +#define XR_USE_GRAPHICS_API_OPENGL +#include + #include #include +#include "controllers/Pose.h" + #define HAND_COUNT 2 constexpr XrPosef XR_INDENTITY_POSE = { diff --git a/plugins/openxr/src/OpenXrDisplayPlugin.cpp b/plugins/openxr/src/OpenXrDisplayPlugin.cpp index e15de872dd..fd9177143a 100644 --- a/plugins/openxr/src/OpenXrDisplayPlugin.cpp +++ b/plugins/openxr/src/OpenXrDisplayPlugin.cpp @@ -2,6 +2,7 @@ // Overte OpenXR Plugin // // Copyright 2024 Lubosz Sarnecki +// Copyright 2024 Overte e.V. // // SPDX-License-Identifier: Apache-2.0 // @@ -16,6 +17,11 @@ #include #include +#if defined(Q_OS_WIN) +#undef near +#undef far +#endif + Q_DECLARE_LOGGING_CATEGORY(xr_display_cat) Q_LOGGING_CATEGORY(xr_display_cat, "openxr.display") @@ -264,7 +270,7 @@ void OpenXrDisplayPlugin::internalDeactivate() { // We can get into a state where activate -> deactivate -> activate is called in a chain. // We are probably gonna have a bad time then. At least check if the session is already running. // This happens when the application decides to switch display plugins back and forth. This should - // prbably be fixed there. + // probably be fixed there. if (_context->_isSessionRunning) { if (!_context->requestExitSession()) { qCCritical(xr_display_cat, "Failed to request exit session"); diff --git a/plugins/openxr/src/OpenXrDisplayPlugin.h b/plugins/openxr/src/OpenXrDisplayPlugin.h index e36dba44f5..ac83c8e094 100644 --- a/plugins/openxr/src/OpenXrDisplayPlugin.h +++ b/plugins/openxr/src/OpenXrDisplayPlugin.h @@ -2,6 +2,7 @@ // Overte OpenXR Plugin // // Copyright 2024 Lubosz Sarnecki +// Copyright 2024 Overte e.V. // // SPDX-License-Identifier: Apache-2.0 // @@ -13,15 +14,6 @@ #include "OpenXrContext.h" -#include "gpu/gl/GLBackend.h" - -#include - -#define XR_USE_PLATFORM_XLIB -#define XR_USE_GRAPHICS_API_OPENGL -#include -#include - class OpenXrDisplayPlugin : public HmdDisplayPlugin { public: OpenXrDisplayPlugin(std::shared_ptr c); diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index 04582ae92f..c16581efb5 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -2,6 +2,7 @@ // Overte OpenXR Plugin // // Copyright 2024 Lubosz Sarnecki +// Copyright 2024 Overte e.V. // // SPDX-License-Identifier: Apache-2.0 // diff --git a/plugins/openxr/src/OpenXrInputPlugin.h b/plugins/openxr/src/OpenXrInputPlugin.h index f1a578867d..a3c29794e0 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.h +++ b/plugins/openxr/src/OpenXrInputPlugin.h @@ -2,6 +2,7 @@ // Overte OpenXR Plugin // // Copyright 2024 Lubosz Sarnecki +// Copyright 2024 Overte e.V. // // SPDX-License-Identifier: Apache-2.0 // diff --git a/plugins/openxr/src/OpenXrProvider.cpp b/plugins/openxr/src/OpenXrProvider.cpp index c2a87dff67..ba23a882cb 100644 --- a/plugins/openxr/src/OpenXrProvider.cpp +++ b/plugins/openxr/src/OpenXrProvider.cpp @@ -2,6 +2,7 @@ // Overte OpenXR Plugin // // Copyright 2024 Lubosz Sarnecki +// Copyright 2024 Overte e.V. // // SPDX-License-Identifier: Apache-2.0 // From 710da838d8867cacd2427bc0db5e5e914eda628f Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Wed, 4 Sep 2024 21:17:26 +0200 Subject: [PATCH 20/43] OpenXr: Use C++20 std::optional. --- plugins/openxr/src/OpenXrContext.h | 5 ++--- plugins/openxr/src/OpenXrDisplayPlugin.cpp | 17 ++++++++--------- plugins/openxr/src/OpenXrDisplayPlugin.h | 4 +--- plugins/openxr/src/OpenXrInputPlugin.cpp | 4 ++-- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/plugins/openxr/src/OpenXrContext.h b/plugins/openxr/src/OpenXrContext.h index eed18eeeb8..82baf098f5 100644 --- a/plugins/openxr/src/OpenXrContext.h +++ b/plugins/openxr/src/OpenXrContext.h @@ -9,6 +9,7 @@ #pragma once +#include #include @@ -58,9 +59,7 @@ public: XrPath _handPaths[HAND_COUNT]; controller::Pose _lastHeadPose; - XrTime _lastPredictedDisplayTime; - // TODO: Enable C++17 and use std::optional - bool _lastPredictedDisplayTimeInitialized = false; + std::optional _lastPredictedDisplayTime; bool _shouldQuit = false; bool _shouldRunFrameCycle = false; diff --git a/plugins/openxr/src/OpenXrDisplayPlugin.cpp b/plugins/openxr/src/OpenXrDisplayPlugin.cpp index fd9177143a..efd8952a7e 100644 --- a/plugins/openxr/src/OpenXrDisplayPlugin.cpp +++ b/plugins/openxr/src/OpenXrDisplayPlugin.cpp @@ -67,13 +67,13 @@ inline static glm::mat4 fovToProjection(const XrFovf fov, const float near, cons } glm::mat4 OpenXrDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { - if (!_viewsInitialized) { + if (!_views.has_value()) { return baseProjection; } ViewFrustum frustum; frustum.setProjection(baseProjection); - return fovToProjection(_views[(eye == Left) ? 0 : 1].fov, frustum.getNearClip(), frustum.getFarClip()); + return fovToProjection(_views.value()[(eye == Left) ? 0 : 1].fov, frustum.getNearClip(), frustum.getFarClip()); } // TODO: This apparently wasn't right in the OpenVR plugin, but this is what it basically did. @@ -99,9 +99,11 @@ bool OpenXrDisplayPlugin::initViews() { assert(_viewCount != 0); + _views = std::vector(); + for (uint32_t i = 0; i < _viewCount; i++) { XrView view = { .type = XR_TYPE_VIEW }; - _views.push_back(view); + _views.value().push_back(view); XrViewConfigurationView viewConfig = { .type = XR_TYPE_VIEW_CONFIGURATION_VIEW }; _viewConfigs.push_back(viewConfig); @@ -363,7 +365,6 @@ bool OpenXrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { return false; _context->_lastPredictedDisplayTime = _lastFrameState.predictedDisplayTime; - _context->_lastPredictedDisplayTimeInitialized = true; std::vector eye_views(_viewCount); for (uint32_t i = 0; i < _viewCount; i++) { @@ -399,17 +400,15 @@ bool OpenXrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { .space = _context->_stageSpace, }; - result = xrLocateViews(_context->_session, &viewLocateInfo, &_lastViewState, _viewCount, &_viewCount, _views.data()); + 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[i].pose; - _projectionLayerViews[i].fov = _views[i].fov; + _projectionLayerViews[i].pose = _views.value()[i].pose; + _projectionLayerViews[i].fov = _views.value()[i].fov; } - _viewsInitialized = true; - XrSpaceLocation headLocation = { .type = XR_TYPE_SPACE_LOCATION, .pose = XR_INDENTITY_POSE, diff --git a/plugins/openxr/src/OpenXrDisplayPlugin.h b/plugins/openxr/src/OpenXrDisplayPlugin.h index ac83c8e094..01df5fcf27 100644 --- a/plugins/openxr/src/OpenXrDisplayPlugin.h +++ b/plugins/openxr/src/OpenXrDisplayPlugin.h @@ -66,9 +66,7 @@ private: uint32_t _viewCount = 0; std::vector _projectionLayerViews; - std::vector _views; - // TODO: Enable C++17 and use std::optional - bool _viewsInitialized = false; + std::optional> _views; std::vector _viewConfigs; diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index c16581efb5..f9823adf70 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -228,8 +228,8 @@ XrSpaceLocation OpenXrInputPlugin::Action::getPose(uint32_t handId) { .type = XR_TYPE_SPACE_LOCATION, }; - if (_context->_lastPredictedDisplayTimeInitialized) { - result = xrLocateSpace(_poseSpaces[handId], _context->_stageSpace, _context->_lastPredictedDisplayTime, &location); + if (_context->_lastPredictedDisplayTime.has_value()) { + result = xrLocateSpace(_poseSpaces[handId], _context->_stageSpace, _context->_lastPredictedDisplayTime.value(), &location); xrCheck(_context->_instance, result, "Failed to locate hand space!"); } From aa6facf7e67e8f94cf500a29e612a343bd0ec8ab Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Wed, 4 Sep 2024 21:20:40 +0200 Subject: [PATCH 21/43] OpenXrInputPlugin: Use C++20 map iterators. --- plugins/openxr/src/OpenXrInputPlugin.cpp | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index f9823adf70..f867efd1c3 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -385,12 +385,12 @@ bool OpenXrInputPlugin::InputDevice::initActions() { }; // clang-format on - for (const auto& pathAndType : actionsToInit) { - std::shared_ptr action = std::make_shared(_context, pathAndType.second, pathAndType.first); + for (const auto& [path, type] : actionsToInit) { + std::shared_ptr action = std::make_shared(_context, type, path); if (!action->init(_actionSet)) { - qCCritical(xr_input_cat, "Creating action %s failed!", pathAndType.first.c_str()); + qCCritical(xr_input_cat, "Creating action %s failed!", path.c_str()); } else { - _actions.emplace(pathAndType.first, action); + _actions.emplace(path, action); } } @@ -516,12 +516,12 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I }; for (uint32_t i = 0; i < HAND_COUNT; i++) { - for (const auto& channelAndPath : axesToUpdate[i]) { - _axisStateMap[channelAndPath.first].value = _actions.at(channelAndPath.second)->getFloat(i).currentState; + for (const auto& [channel, path] : axesToUpdate[i]) { + _axisStateMap[channel].value = _actions.at(path)->getFloat(i).currentState; - // if (_axisStateMap[channelAndPath.first].value != 0) { - // qCDebug(xr_input_cat, "🐸 Controller %d: %s (%d): %f", i, channelAndPath.second.c_str(), channelAndPath.first, - // (double)_axisStateMap[channelAndPath.first].value); + // if (_axisStateMap[channel].value != 0) { + // qCDebug(xr_input_cat, "🐸 Controller %d: %s (%d): %f", i, path.c_str(), channel, + // (double)_axisStateMap[channel].value); // } } } @@ -553,10 +553,10 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I }; for (uint32_t i = 0; i < HAND_COUNT; i++) { - for (const auto& channelAndPath : buttonsToUpdate[i]) { - if (_actions.at(channelAndPath.second)->getBool(i).currentState == XR_TRUE) { - _buttonPressedMap.insert(channelAndPath.first); - // qCDebug(xr_input_cat, "🐸 Controller %d: %s (%d)", i, channelAndPath.second.c_str(), channelAndPath.first); + for (const auto& [channel, path] : buttonsToUpdate[i]) { + if (_actions.at(path)->getBool(i).currentState == XR_TRUE) { + _buttonPressedMap.insert(channel); + // qCDebug(xr_input_cat, "🐸 Controller %d: %s (%d)", i, path.c_str(), channel); } } } From 760b846bb1d705c5127b9b364a6ae0c437ee3e96 Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Wed, 4 Sep 2024 21:51:33 +0200 Subject: [PATCH 22/43] OpenXrDisplayPlugin: Use C++20 std::format. --- plugins/openxr/src/OpenXrDisplayPlugin.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/plugins/openxr/src/OpenXrDisplayPlugin.cpp b/plugins/openxr/src/OpenXrDisplayPlugin.cpp index efd8952a7e..29843bddeb 100644 --- a/plugins/openxr/src/OpenXrDisplayPlugin.cpp +++ b/plugins/openxr/src/OpenXrDisplayPlugin.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #if defined(Q_OS_WIN) #undef near @@ -133,12 +134,8 @@ static std::string glFormatStr(GLenum source) { ENUM_TO_STR(GL_RGBA16); ENUM_TO_STR(GL_RGBA16F); ENUM_TO_STR(GL_SRGB8_ALPHA8); - default: { - // TODO: Enable C++20 for std::format - std::ostringstream ss; - ss << "0x" << std::hex << source; - return ss.str(); - } + default: + return std::format("0x{:X}", source); } } From 1101a2aeb46fcb9efec8f5ecf3f805be55728621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Thu, 5 Sep 2024 14:09:52 +0200 Subject: [PATCH 23/43] Install missing xcb/glx.h dependency. --- .github/workflows/pr_build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr_build.yml b/.github/workflows/pr_build.yml index 1494389521..cffa31f143 100644 --- a/.github/workflows/pr_build.yml +++ b/.github/workflows/pr_build.yml @@ -64,7 +64,7 @@ jobs: runner: [self_hosted, type-cx52, image-x86-app-docker-ce] arch: amd64 build_type: full - # apt-dependencies: # add missing dependencies to docker image when convenient + apt-dependencies: libxcb-glx0-dev # add missing dependencies to docker image when convenient image: docker.io/overte/overte-full-build:0.1.6-ubuntu-20.04-amd64 # Android builds are currently failing #- os: ubuntu-18.04 @@ -75,8 +75,8 @@ jobs: runner: [self_hosted, type-cax41, image-arm-app-docker-ce] arch: aarch64 build_type: full + apt-dependencies: libxcb-glx0-dev # add missing dependencies to docker image when convenient image: docker.io/overte/overte-full-build:0.1.6-ubuntu-22.04-aarch64 - # apt-dependencies: # add missing dependencies to docker image when convenient fail-fast: false runs-on: ${{matrix.runner}} container: ${{matrix.image}} From 2d0db677aa2e2e265915e5ad76024637cb036b2e Mon Sep 17 00:00:00 2001 From: Lubosz Sarnecki Date: Sat, 7 Sep 2024 15:37:22 +0200 Subject: [PATCH 24/43] pr_build: Get GCC 13 on Ubuntu using the ppa:ubuntu-toolchain-r/test. --- .github/workflows/pr_build.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/pr_build.yml b/.github/workflows/pr_build.yml index cffa31f143..507f39f53d 100644 --- a/.github/workflows/pr_build.yml +++ b/.github/workflows/pr_build.yml @@ -184,12 +184,21 @@ jobs: run: | if [[ "${{ matrix.os }}" =~ "Ubuntu" || "${{ matrix.os }}" =~ "Debian" ]]; then + echo "Adding Toolchain test PPA" + add-apt-repository ppa:ubuntu-toolchain-r/test + echo "Updating apt repository index" sudo apt update || exit 1 echo "Installing apt packages" sudo apt install -y ${{ matrix.apt-dependencies }} || exit 1 + echo "Installing gcc-13" + apt install -y gcc-13 g++-13 || exit 1 + + # Set GCC 13 as default + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100 --slave /usr/bin/g++ g++ /usr/bin/g++-13 --slave /usr/bin/gcov gcov /usr/bin/gcov-13 + else # macOS echo "Downloading MacOSX10.12 SDK.." curl --progress-bar -L -o macOS_SDK10.12.4.tar.xz "https://data.moto9000.moe/overte_packages/macOS_SDK10.12.4.tar.xz" || exit 1 From af00bf5667707254a87d49c062919feb59d3ec1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Sat, 7 Sep 2024 22:50:50 +0200 Subject: [PATCH 25/43] Make sure add-apt-repository is available. --- .github/workflows/pr_build.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr_build.yml b/.github/workflows/pr_build.yml index 507f39f53d..1458aad9bc 100644 --- a/.github/workflows/pr_build.yml +++ b/.github/workflows/pr_build.yml @@ -184,15 +184,16 @@ jobs: run: | if [[ "${{ matrix.os }}" =~ "Ubuntu" || "${{ matrix.os }}" =~ "Debian" ]]; then - echo "Adding Toolchain test PPA" - add-apt-repository ppa:ubuntu-toolchain-r/test - echo "Updating apt repository index" sudo apt update || exit 1 echo "Installing apt packages" sudo apt install -y ${{ matrix.apt-dependencies }} || exit 1 + echo "Adding Toolchain test PPA" + apt install -y software-properties-common + add-apt-repository ppa:ubuntu-toolchain-r/test + echo "Installing gcc-13" apt install -y gcc-13 g++-13 || exit 1 From 7ece6e7a0ccbcd7c09142c385a06e81037bcbfae Mon Sep 17 00:00:00 2001 From: Ada Date: Thu, 17 Oct 2024 09:55:50 +1000 Subject: [PATCH 26/43] OpenXrInputPlugin: Hack for Vive controllers This aught to be replaced with a proper set of XR input profiles, at the moment this is *just* enough to get hardcoded button inputs working with the existing Vive controller config. --- interface/src/Application.cpp | 5 + plugins/openxr/src/OpenXrInputPlugin.cpp | 254 +++++++++++++++++------ plugins/openxr/src/OpenXrInputPlugin.h | 2 + 3 files changed, 203 insertions(+), 58 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 538b17f3a6..c434b4b7f6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2373,6 +2373,11 @@ void Application::update(float deltaTime) { AnimDebugDraw::getInstance().update(); } + // a hack to prevent the engine from trying + // to pump out hundreds and hundreds of simulation + // ticks per second that can't be displayed + std::this_thread::sleep_for(5ms); + { // Game loop is done, mark the end of the frame for the scene transactions and the render loop to take over PerformanceTimer perfTimer("enqueueFrame"); diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index f867efd1c3..ae72d41230 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -7,6 +7,8 @@ // SPDX-License-Identifier: Apache-2.0 // +#include + #include "OpenXrInputPlugin.h" #include "AvatarConstants.h" @@ -100,7 +102,7 @@ void OpenXrInputPlugin::loadSettings() { void OpenXrInputPlugin::saveSettings() const { } -OpenXrInputPlugin::InputDevice::InputDevice(std::shared_ptr c) : controller::InputDevice("Index") { +OpenXrInputPlugin::InputDevice::InputDevice(std::shared_ptr c) : controller::InputDevice("Vive") { _context = c; } @@ -303,40 +305,120 @@ controller::Input::NamedVector OpenXrInputPlugin::InputDevice::getAvailableInput // clang-format off QVector availableInputs{ - // Poses - makePair(LEFT_HAND, "LeftHand"), - makePair(RIGHT_HAND, "RightHand"), - makePair(HEAD, "Head"), - // Sticks + // Trackpad analogs makePair(LX, "LX"), makePair(LY, "LY"), - makePair(LS, "LS"), - makePair(LS_TOUCH, "LSTouch"), makePair(RX, "RX"), makePair(RY, "RY"), - makePair(RS, "RS"), - makePair(RS_TOUCH, "RSTouch"), - // Face buttons - makePair(RIGHT_PRIMARY_THUMB, "RightPrimaryThumb"), - makePair(RIGHT_PRIMARY_THUMB_TOUCH, "RightPrimaryThumbTouch"), - makePair(RIGHT_SECONDARY_THUMB, "RightSecondaryThumb"), - makePair(RIGHT_SECONDARY_THUMB_TOUCH, "RightSecondaryThumbTouch"), - makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"), - makePair(LEFT_PRIMARY_THUMB_TOUCH, "LeftPrimaryThumbTouch"), - makePair(LEFT_SECONDARY_THUMB, "LeftSecondaryThumb"), - makePair(LEFT_SECONDARY_THUMB_TOUCH, "LeftSecondaryThumbTouch"), - // Triggers - makePair(RT, "RT"), + // capacitive touch on the touch pad + makePair(LS_TOUCH, "LSTouch"), + makePair(RS_TOUCH, "RSTouch"), + + // touch pad press + makePair(LS, "LS"), + makePair(RS, "RS"), + // Differentiate where we are in the touch pad click + makePair(LS_CENTER, "LSCenter"), + makePair(LS_X, "LSX"), + makePair(LS_Y, "LSY"), + makePair(RS_CENTER, "RSCenter"), + makePair(RS_X, "RSX"), + makePair(RS_Y, "RSY"), + + + // triggers makePair(LT, "LT"), - makePair(RT_CLICK, "RTClick"), + makePair(RT, "RT"), + + // Trigger clicks makePair(LT_CLICK, "LTClick"), - makePair(LEFT_PRIMARY_INDEX_TOUCH, "LeftPrimaryIndexTouch"), - makePair(RIGHT_PRIMARY_INDEX_TOUCH, "RightPrimaryIndexTouch"), - // Menu buttons - // TODO: Add this to button channel - // Input::NamedPair(Input(_deviceID, LEFT_APP_MENU, ChannelType::BUTTON), "LeftApplicationMenu"), - // Input::NamedPair(Input(_deviceID, RIGHT_APP_MENU, ChannelType::BUTTON), "RightApplicationMenu"), + makePair(RT_CLICK, "RTClick"), + + // low profile side grip button. + makePair(LEFT_GRIP, "LeftGrip"), + makePair(RIGHT_GRIP, "RightGrip"), + + // 3d location of left controller and fingers + makePair(LEFT_HAND, "LeftHand"), + makePair(LEFT_HAND_THUMB1, "LeftHandThumb1"), + makePair(LEFT_HAND_THUMB2, "LeftHandThumb2"), + makePair(LEFT_HAND_THUMB3, "LeftHandThumb3"), + makePair(LEFT_HAND_THUMB4, "LeftHandThumb4"), + makePair(LEFT_HAND_INDEX1, "LeftHandIndex1"), + makePair(LEFT_HAND_INDEX2, "LeftHandIndex2"), + makePair(LEFT_HAND_INDEX3, "LeftHandIndex3"), + makePair(LEFT_HAND_INDEX4, "LeftHandIndex4"), + makePair(LEFT_HAND_MIDDLE1, "LeftHandMiddle1"), + makePair(LEFT_HAND_MIDDLE2, "LeftHandMiddle2"), + makePair(LEFT_HAND_MIDDLE3, "LeftHandMiddle3"), + makePair(LEFT_HAND_MIDDLE4, "LeftHandMiddle4"), + makePair(LEFT_HAND_RING1, "LeftHandRing1"), + makePair(LEFT_HAND_RING2, "LeftHandRing2"), + makePair(LEFT_HAND_RING3, "LeftHandRing3"), + makePair(LEFT_HAND_RING4, "LeftHandRing4"), + makePair(LEFT_HAND_PINKY1, "LeftHandPinky1"), + makePair(LEFT_HAND_PINKY2, "LeftHandPinky2"), + makePair(LEFT_HAND_PINKY3, "LeftHandPinky3"), + makePair(LEFT_HAND_PINKY4, "LeftHandPinky4"), + + // 3d location of right controller and fingers + makePair(RIGHT_HAND, "RightHand"), + makePair(RIGHT_HAND_THUMB1, "RightHandThumb1"), + makePair(RIGHT_HAND_THUMB2, "RightHandThumb2"), + makePair(RIGHT_HAND_THUMB3, "RightHandThumb3"), + makePair(RIGHT_HAND_THUMB4, "RightHandThumb4"), + makePair(RIGHT_HAND_INDEX1, "RightHandIndex1"), + makePair(RIGHT_HAND_INDEX2, "RightHandIndex2"), + makePair(RIGHT_HAND_INDEX3, "RightHandIndex3"), + makePair(RIGHT_HAND_INDEX4, "RightHandIndex4"), + makePair(RIGHT_HAND_MIDDLE1, "RightHandMiddle1"), + makePair(RIGHT_HAND_MIDDLE2, "RightHandMiddle2"), + makePair(RIGHT_HAND_MIDDLE3, "RightHandMiddle3"), + makePair(RIGHT_HAND_MIDDLE4, "RightHandMiddle4"), + makePair(RIGHT_HAND_RING1, "RightHandRing1"), + makePair(RIGHT_HAND_RING2, "RightHandRing2"), + makePair(RIGHT_HAND_RING3, "RightHandRing3"), + makePair(RIGHT_HAND_RING4, "RightHandRing4"), + makePair(RIGHT_HAND_PINKY1, "RightHandPinky1"), + makePair(RIGHT_HAND_PINKY2, "RightHandPinky2"), + makePair(RIGHT_HAND_PINKY3, "RightHandPinky3"), + makePair(RIGHT_HAND_PINKY4, "RightHandPinky4"), + + makePair(LEFT_FOOT, "LeftFoot"), + makePair(RIGHT_FOOT, "RightFoot"), + makePair(HIPS, "Hips"), + makePair(SPINE2, "Spine2"), + makePair(HEAD, "Head"), + makePair(LEFT_ARM, "LeftArm"), + makePair(RIGHT_ARM, "RightArm"), + makePair(LEFT_EYE, "LeftEye"), + makePair(RIGHT_EYE, "RightEye"), + makePair(EYEBLINK_L, "EyeBlink_L"), + makePair(EYEBLINK_R, "EyeBlink_R"), + + // 16 tracked poses + makePair(TRACKED_OBJECT_00, "TrackedObject00"), + makePair(TRACKED_OBJECT_01, "TrackedObject01"), + makePair(TRACKED_OBJECT_02, "TrackedObject02"), + makePair(TRACKED_OBJECT_03, "TrackedObject03"), + makePair(TRACKED_OBJECT_04, "TrackedObject04"), + makePair(TRACKED_OBJECT_05, "TrackedObject05"), + makePair(TRACKED_OBJECT_06, "TrackedObject06"), + makePair(TRACKED_OBJECT_07, "TrackedObject07"), + makePair(TRACKED_OBJECT_08, "TrackedObject08"), + makePair(TRACKED_OBJECT_09, "TrackedObject09"), + makePair(TRACKED_OBJECT_10, "TrackedObject10"), + makePair(TRACKED_OBJECT_11, "TrackedObject11"), + makePair(TRACKED_OBJECT_12, "TrackedObject12"), + makePair(TRACKED_OBJECT_13, "TrackedObject13"), + makePair(TRACKED_OBJECT_14, "TrackedObject14"), + makePair(TRACKED_OBJECT_15, "TrackedObject15"), + + // application buttons, system buttons are unused(?) + // in openxr but it opens the dashboard in steamvr + makePair(LEFT_SECONDARY_THUMB, "LeftApplicationMenu"), + makePair(RIGHT_SECONDARY_THUMB, "RightApplicationMenu"), }; // clang-format on @@ -344,7 +426,7 @@ controller::Input::NamedVector OpenXrInputPlugin::InputDevice::getAvailableInput } QString OpenXrInputPlugin::InputDevice::getDefaultMappingConfig() const { - return PathUtils::resourcesPath() + "/controllers/openxr_index.json"; + return PathUtils::resourcesPath() + "/controllers/vive.json"; } bool OpenXrInputPlugin::InputDevice::initActions() { @@ -365,6 +447,23 @@ bool OpenXrInputPlugin::InputDevice::initActions() { if (!xrCheck(instance, result, "Failed to create action set.")) return false; +#if 1 + std::map actionsToInit = { + { "/input/trackpad/x", XR_ACTION_TYPE_FLOAT_INPUT }, + { "/input/trackpad/y", XR_ACTION_TYPE_FLOAT_INPUT }, + { "/input/trackpad/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/trackpad/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/trigger/value", XR_ACTION_TYPE_FLOAT_INPUT }, + { "/input/trigger/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/trigger/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/squeeze/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/system/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/menu/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/grip/pose", XR_ACTION_TYPE_POSE_INPUT }, + { "/input/aim/pose", XR_ACTION_TYPE_POSE_INPUT }, + { "/output/haptic", XR_ACTION_TYPE_VIBRATION_OUTPUT }, + }; +#else // clang-format off std::map actionsToInit = { { "/input/thumbstick/x", XR_ACTION_TYPE_FLOAT_INPUT }, @@ -384,6 +483,7 @@ bool OpenXrInputPlugin::InputDevice::initActions() { { "/input/system/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, }; // clang-format on +#endif for (const auto& [path, type] : actionsToInit) { std::shared_ptr action = std::make_shared(_context, type, path); @@ -394,6 +494,7 @@ bool OpenXrInputPlugin::InputDevice::initActions() { } } +#if 0 // Khronos Simple Controller std::vector simpleBindings = { "/input/grip/pose", @@ -404,6 +505,29 @@ bool OpenXrInputPlugin::InputDevice::initActions() { if (!initBindings("/interaction_profiles/khr/simple_controller", simpleBindings)) { qCCritical(xr_input_cat, "Failed to init bindings."); } +#endif + +#if 1 + // HTC Vive Wand + std::vector viveWandBindings = { + "/input/system/click", + "/input/squeeze/click", + "/input/menu/click", + "/input/trigger/click", + "/input/trigger/value", + "/input/trackpad/x", + "/input/trackpad/y", + "/input/trackpad/click", + "/input/trackpad/touch", + "/input/grip/pose", + "/input/aim/pose", + "/output/haptic", + }; + + if (!initBindings("/interaction_profiles/htc/vive_controller", viveWandBindings)) { + qCCritical(xr_input_cat, "Failed to init bindings."); + } +#else // Valve Index Controller // clang-format off @@ -428,6 +552,7 @@ bool OpenXrInputPlugin::InputDevice::initActions() { if (!initBindings("/interaction_profiles/valve/index_controller", indexBindings)) { qCCritical(xr_input_cat, "Failed to init bindings."); } +#endif XrSessionActionSetsAttachInfo attachInfo = { .type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO, @@ -494,8 +619,15 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I quat rotation = xrQuatToGlm(handLocation.pose.orientation); auto pose = controller::Pose(translation, rotation); glm::mat4 handOffset = i == 0 ? glm::toMat4(leftRotationOffset) : glm::toMat4(rightRotationOffset); + + // offset constants taken from OpenComposite + // (apparently the openvr plugin uses similar constants?) + glm::mat4 posOffset(1.0f); + posOffset *= glm::translate(glm::vec3(handOffset[0]) * (i == 0 ? 0.04f : -0.04f)); + posOffset *= glm::translate(glm::vec3(handOffset[1]) * -0.15f); + posOffset *= glm::translate(glm::vec3(handOffset[2]) * 0.02f); _poseStateMap[i == 0 ? controller::LEFT_HAND : controller::RIGHT_HAND] = - pose.postTransform(handOffset).transform(sensorToAvatar); + pose.postTransform(posOffset).postTransform(handOffset).transform(sensorToAvatar); } } @@ -504,13 +636,13 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I std::map axesToUpdate[2] = { { - { controller::LX, "/input/thumbstick/x" }, - { controller::LY, "/input/thumbstick/y" }, + { controller::LX, "/input/trackpad/x" }, + { controller::LY, "/input/trackpad/y" }, { controller::LT, "/input/trigger/value" }, }, { - { controller::RX, "/input/thumbstick/x" }, - { controller::RY, "/input/thumbstick/y" }, + { controller::RX, "/input/trackpad/x" }, + { controller::RY, "/input/trackpad/y" }, { controller::RT, "/input/trigger/value" }, }, }; @@ -518,37 +650,21 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I for (uint32_t i = 0; i < HAND_COUNT; i++) { for (const auto& [channel, path] : axesToUpdate[i]) { _axisStateMap[channel].value = _actions.at(path)->getFloat(i).currentState; - - // if (_axisStateMap[channel].value != 0) { - // qCDebug(xr_input_cat, "🐸 Controller %d: %s (%d): %f", i, path.c_str(), channel, - // (double)_axisStateMap[channel].value); - // } } } - // TODO: Figure out why LEFT_APP_MENU is misssing in StandardButtonChannel std::map buttonsToUpdate[2] = { { - { controller::LEFT_PRIMARY_THUMB, "/input/a/click" }, - { controller::LEFT_PRIMARY_THUMB_TOUCH, "/input/a/touch" }, - { controller::LEFT_SECONDARY_THUMB, "/input/b/click" }, - { controller::LEFT_SECONDARY_THUMB_TOUCH, "/input/b/touch" }, + { controller::LS, "/input/trackpad/click" }, { controller::LT_CLICK, "/input/trigger/click" }, - { controller::LEFT_PRIMARY_INDEX_TOUCH, "/input/trigger/touch" }, - { controller::LS, "/input/thumbstick/click" }, - { controller::LS_TOUCH, "/input/thumbstick/touch" }, - //{ LEFT_APP_MENU, "/input/system/click" }, + { controller::LS_TOUCH, "/input/trackpad/touch" }, + { controller::LEFT_SECONDARY_THUMB, "/input/menu/click" }, }, { - { controller::RIGHT_PRIMARY_THUMB, "/input/a/click" }, - { controller::RIGHT_PRIMARY_THUMB_TOUCH, "/input/a/touch" }, - { controller::RIGHT_SECONDARY_THUMB, "/input/b/click" }, - { controller::RIGHT_SECONDARY_THUMB_TOUCH, "/input/b/touch" }, + { controller::RS, "/input/trackpad/click" }, { controller::RT_CLICK, "/input/trigger/click" }, - { controller::RIGHT_PRIMARY_INDEX_TOUCH, "/input/trigger/touch" }, - { controller::RS, "/input/thumbstick/click" }, - { controller::RS_TOUCH, "/input/thumbstick/touch" }, - //{ RIGHT_APP_MENU, "/input/system/click" }, + { controller::RS_TOUCH, "/input/trackpad/touch" }, + { controller::RIGHT_SECONDARY_THUMB, "/input/menu/click" }, }, }; @@ -556,8 +672,30 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I for (const auto& [channel, path] : buttonsToUpdate[i]) { if (_actions.at(path)->getBool(i).currentState == XR_TRUE) { _buttonPressedMap.insert(channel); - // qCDebug(xr_input_cat, "🐸 Controller %d: %s (%d)", i, path.c_str(), channel); } } } -} \ No newline at end of file + + partitionTouchpad(controller::LS, controller::LX, controller::LY, controller::LS_CENTER, controller::LS_X, controller::LS_Y); + partitionTouchpad(controller::RS, controller::RX, controller::RY, controller::RS_CENTER, controller::RS_X, controller::RS_Y); + + // hack to convert the boolean squeeze buttons into axes + _axisStateMap[controller::LEFT_GRIP].value = _actions.at("/input/squeeze/click")->getBool(0).currentState ? 1.0f : 0.0f; + _axisStateMap[controller::RIGHT_GRIP].value = _actions.at("/input/squeeze/click")->getBool(1).currentState ? 1.0f : 0.0f; +} + +// copied from openvr/ViveControllerManager +void OpenXrInputPlugin::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton) { + // Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values. + const float CENTER_DEADBAND = 0.6f; + const float DIAGONAL_DIVIDE_IN_RADIANS = PI / 4.0f; + if (_buttonPressedMap.find(sButton) != _buttonPressedMap.end()) { + float absX = abs(_axisStateMap[xAxis].value); + float absY = abs(_axisStateMap[yAxis].value); + glm::vec2 cartesianQuadrantI(absX, absY); + float angle = glm::atan(cartesianQuadrantI.y / cartesianQuadrantI.x); + float radius = glm::length(cartesianQuadrantI); + bool isCenter = radius < CENTER_DEADBAND; + _buttonPressedMap.insert(isCenter ? centerPseudoButton : ((angle < DIAGONAL_DIVIDE_IN_RADIANS) ? xPseudoButton :yPseudoButton)); + } +} diff --git a/plugins/openxr/src/OpenXrInputPlugin.h b/plugins/openxr/src/OpenXrInputPlugin.h index a3c29794e0..54e3011159 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.h +++ b/plugins/openxr/src/OpenXrInputPlugin.h @@ -80,6 +80,8 @@ private: void focusOutEvent() override; bool triggerHapticPulse(float strength, float duration, uint16_t index) override; + void partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton); + mutable std::recursive_mutex _lock; template void withLock(F&& f) { From dc3a508051fdc998a8d2c695dc5a26dd5b465ac8 Mon Sep 17 00:00:00 2001 From: Ada Date: Sat, 26 Oct 2024 13:15:16 +1000 Subject: [PATCH 27/43] OpenXrInputPlugin: Make actions more generic This still isn't ideal and it uses the Vive controller settings, but this should work on most controllers thanks to OpenXR's built-in action remapping and standardised button names. --- plugins/openxr/src/OpenXrInputPlugin.cpp | 189 ++++++++--------------- 1 file changed, 65 insertions(+), 124 deletions(-) diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index ae72d41230..0ebe70f439 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -284,6 +284,10 @@ bool OpenXrInputPlugin::InputDevice::initBindings(const std::string& profileName std::vector bindings; for (const std::string& path : actionsToBind) { + if (!_actions.contains(path)) { + qCWarning(xr_input_cat, "%s has unbound input %s", profileName.c_str(), path.c_str()); + continue; + } std::vector actionBindings = _actions.at(path)->getBindings(); bindings.insert(std::end(bindings), std::begin(actionBindings), std::end(actionBindings)); } @@ -305,6 +309,10 @@ controller::Input::NamedVector OpenXrInputPlugin::InputDevice::getAvailableInput // clang-format off QVector availableInputs{ + makePair(HEAD, "Head"), + makePair(LEFT_HAND, "LeftHand"), + makePair(RIGHT_HAND, "RightHand"), + // Trackpad analogs makePair(LX, "LX"), makePair(LY, "LY"), @@ -326,11 +334,9 @@ controller::Input::NamedVector OpenXrInputPlugin::InputDevice::getAvailableInput makePair(RS_X, "RSX"), makePair(RS_Y, "RSY"), - // triggers makePair(LT, "LT"), makePair(RT, "RT"), - // Trigger clicks makePair(LT_CLICK, "LTClick"), makePair(RT_CLICK, "RTClick"), @@ -339,84 +345,11 @@ controller::Input::NamedVector OpenXrInputPlugin::InputDevice::getAvailableInput makePair(LEFT_GRIP, "LeftGrip"), makePair(RIGHT_GRIP, "RightGrip"), - // 3d location of left controller and fingers - makePair(LEFT_HAND, "LeftHand"), - makePair(LEFT_HAND_THUMB1, "LeftHandThumb1"), - makePair(LEFT_HAND_THUMB2, "LeftHandThumb2"), - makePair(LEFT_HAND_THUMB3, "LeftHandThumb3"), - makePair(LEFT_HAND_THUMB4, "LeftHandThumb4"), - makePair(LEFT_HAND_INDEX1, "LeftHandIndex1"), - makePair(LEFT_HAND_INDEX2, "LeftHandIndex2"), - makePair(LEFT_HAND_INDEX3, "LeftHandIndex3"), - makePair(LEFT_HAND_INDEX4, "LeftHandIndex4"), - makePair(LEFT_HAND_MIDDLE1, "LeftHandMiddle1"), - makePair(LEFT_HAND_MIDDLE2, "LeftHandMiddle2"), - makePair(LEFT_HAND_MIDDLE3, "LeftHandMiddle3"), - makePair(LEFT_HAND_MIDDLE4, "LeftHandMiddle4"), - makePair(LEFT_HAND_RING1, "LeftHandRing1"), - makePair(LEFT_HAND_RING2, "LeftHandRing2"), - makePair(LEFT_HAND_RING3, "LeftHandRing3"), - makePair(LEFT_HAND_RING4, "LeftHandRing4"), - makePair(LEFT_HAND_PINKY1, "LeftHandPinky1"), - makePair(LEFT_HAND_PINKY2, "LeftHandPinky2"), - makePair(LEFT_HAND_PINKY3, "LeftHandPinky3"), - makePair(LEFT_HAND_PINKY4, "LeftHandPinky4"), + makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"), + makePair(RIGHT_PRIMARY_THUMB, "RightPrimaryThumb"), + makePair(LEFT_SECONDARY_THUMB, "LeftSecondaryThumb"), + makePair(RIGHT_SECONDARY_THUMB, "RightSecondaryThumb"), - // 3d location of right controller and fingers - makePair(RIGHT_HAND, "RightHand"), - makePair(RIGHT_HAND_THUMB1, "RightHandThumb1"), - makePair(RIGHT_HAND_THUMB2, "RightHandThumb2"), - makePair(RIGHT_HAND_THUMB3, "RightHandThumb3"), - makePair(RIGHT_HAND_THUMB4, "RightHandThumb4"), - makePair(RIGHT_HAND_INDEX1, "RightHandIndex1"), - makePair(RIGHT_HAND_INDEX2, "RightHandIndex2"), - makePair(RIGHT_HAND_INDEX3, "RightHandIndex3"), - makePair(RIGHT_HAND_INDEX4, "RightHandIndex4"), - makePair(RIGHT_HAND_MIDDLE1, "RightHandMiddle1"), - makePair(RIGHT_HAND_MIDDLE2, "RightHandMiddle2"), - makePair(RIGHT_HAND_MIDDLE3, "RightHandMiddle3"), - makePair(RIGHT_HAND_MIDDLE4, "RightHandMiddle4"), - makePair(RIGHT_HAND_RING1, "RightHandRing1"), - makePair(RIGHT_HAND_RING2, "RightHandRing2"), - makePair(RIGHT_HAND_RING3, "RightHandRing3"), - makePair(RIGHT_HAND_RING4, "RightHandRing4"), - makePair(RIGHT_HAND_PINKY1, "RightHandPinky1"), - makePair(RIGHT_HAND_PINKY2, "RightHandPinky2"), - makePair(RIGHT_HAND_PINKY3, "RightHandPinky3"), - makePair(RIGHT_HAND_PINKY4, "RightHandPinky4"), - - makePair(LEFT_FOOT, "LeftFoot"), - makePair(RIGHT_FOOT, "RightFoot"), - makePair(HIPS, "Hips"), - makePair(SPINE2, "Spine2"), - makePair(HEAD, "Head"), - makePair(LEFT_ARM, "LeftArm"), - makePair(RIGHT_ARM, "RightArm"), - makePair(LEFT_EYE, "LeftEye"), - makePair(RIGHT_EYE, "RightEye"), - makePair(EYEBLINK_L, "EyeBlink_L"), - makePair(EYEBLINK_R, "EyeBlink_R"), - - // 16 tracked poses - makePair(TRACKED_OBJECT_00, "TrackedObject00"), - makePair(TRACKED_OBJECT_01, "TrackedObject01"), - makePair(TRACKED_OBJECT_02, "TrackedObject02"), - makePair(TRACKED_OBJECT_03, "TrackedObject03"), - makePair(TRACKED_OBJECT_04, "TrackedObject04"), - makePair(TRACKED_OBJECT_05, "TrackedObject05"), - makePair(TRACKED_OBJECT_06, "TrackedObject06"), - makePair(TRACKED_OBJECT_07, "TrackedObject07"), - makePair(TRACKED_OBJECT_08, "TrackedObject08"), - makePair(TRACKED_OBJECT_09, "TrackedObject09"), - makePair(TRACKED_OBJECT_10, "TrackedObject10"), - makePair(TRACKED_OBJECT_11, "TrackedObject11"), - makePair(TRACKED_OBJECT_12, "TrackedObject12"), - makePair(TRACKED_OBJECT_13, "TrackedObject13"), - makePair(TRACKED_OBJECT_14, "TrackedObject14"), - makePair(TRACKED_OBJECT_15, "TrackedObject15"), - - // application buttons, system buttons are unused(?) - // in openxr but it opens the dashboard in steamvr makePair(LEFT_SECONDARY_THUMB, "LeftApplicationMenu"), makePair(RIGHT_SECONDARY_THUMB, "RightApplicationMenu"), }; @@ -426,6 +359,7 @@ controller::Input::NamedVector OpenXrInputPlugin::InputDevice::getAvailableInput } QString OpenXrInputPlugin::InputDevice::getDefaultMappingConfig() const { + // FIXME: for some reason vive works but openxr_generic breaks the vertical trackpad? return PathUtils::resourcesPath() + "/controllers/vive.json"; } @@ -447,43 +381,36 @@ bool OpenXrInputPlugin::InputDevice::initActions() { if (!xrCheck(instance, result, "Failed to create action set.")) return false; -#if 1 + // clang-format off std::map actionsToInit = { + { "/output/haptic", XR_ACTION_TYPE_VIBRATION_OUTPUT }, + { "/input/grip/pose", XR_ACTION_TYPE_POSE_INPUT }, + + // click but pretend it's a float for the trigger actions + { "/input/select/click", XR_ACTION_TYPE_FLOAT_INPUT }, + { "/input/menu/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/system/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/trackpad/x", XR_ACTION_TYPE_FLOAT_INPUT }, { "/input/trackpad/y", XR_ACTION_TYPE_FLOAT_INPUT }, { "/input/trackpad/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, { "/input/trackpad/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/trigger/value", XR_ACTION_TYPE_FLOAT_INPUT }, - { "/input/trigger/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/trigger/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/squeeze/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/system/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/menu/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/grip/pose", XR_ACTION_TYPE_POSE_INPUT }, - { "/input/aim/pose", XR_ACTION_TYPE_POSE_INPUT }, - { "/output/haptic", XR_ACTION_TYPE_VIBRATION_OUTPUT }, - }; -#else - // clang-format off - std::map actionsToInit = { + { "/input/thumbstick/x", XR_ACTION_TYPE_FLOAT_INPUT }, { "/input/thumbstick/y", XR_ACTION_TYPE_FLOAT_INPUT }, { "/input/thumbstick/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, { "/input/thumbstick/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, { "/input/a/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/a/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, { "/input/b/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + { "/input/a/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, { "/input/b/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, + + { "/input/squeeze/click", XR_ACTION_TYPE_FLOAT_INPUT }, { "/input/trigger/value", XR_ACTION_TYPE_FLOAT_INPUT }, { "/input/trigger/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, { "/input/trigger/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/output/haptic", XR_ACTION_TYPE_VIBRATION_OUTPUT }, - { "/input/grip/pose", XR_ACTION_TYPE_POSE_INPUT }, - { "/input/select/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/system/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, }; // clang-format on -#endif for (const auto& [path, type] : actionsToInit) { std::shared_ptr action = std::make_shared(_context, type, path); @@ -494,22 +421,20 @@ bool OpenXrInputPlugin::InputDevice::initActions() { } } -#if 0 // Khronos Simple Controller std::vector simpleBindings = { - "/input/grip/pose", "/input/select/click", + "/input/menu/click", + "/input/grip/pose", "/output/haptic", }; if (!initBindings("/interaction_profiles/khr/simple_controller", simpleBindings)) { - qCCritical(xr_input_cat, "Failed to init bindings."); + qCCritical(xr_input_cat, "Failed to init bindings for khr/simple_controller"); } -#endif -#if 1 - // HTC Vive Wand - std::vector viveWandBindings = { + // HTC Vive + std::vector viveBindings = { "/input/system/click", "/input/squeeze/click", "/input/menu/click", @@ -520,14 +445,12 @@ bool OpenXrInputPlugin::InputDevice::initActions() { "/input/trackpad/click", "/input/trackpad/touch", "/input/grip/pose", - "/input/aim/pose", "/output/haptic", }; - if (!initBindings("/interaction_profiles/htc/vive_controller", viveWandBindings)) { - qCCritical(xr_input_cat, "Failed to init bindings."); + if (!initBindings("/interaction_profiles/htc/vive_controller", viveBindings)) { + qCCritical(xr_input_cat, "Failed to init bindings for htc/vive_controller"); } -#else // Valve Index Controller // clang-format off @@ -550,9 +473,8 @@ bool OpenXrInputPlugin::InputDevice::initActions() { // clang-format on if (!initBindings("/interaction_profiles/valve/index_controller", indexBindings)) { - qCCritical(xr_input_cat, "Failed to init bindings."); + qCCritical(xr_input_cat, "Failed to init bindings for valve/index_controller"); } -#endif XrSessionActionSetsAttachInfo attachInfo = { .type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO, @@ -634,54 +556,73 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I glm::mat4 defaultHeadOffset = createMatFromQuatAndPos(-DEFAULT_AVATAR_HEAD_ROT, -DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET); _poseStateMap[controller::HEAD] = _context->_lastHeadPose.postTransform(defaultHeadOffset).transform(sensorToAvatar); - std::map axesToUpdate[2] = { + std::vector> axesToUpdate[2] = { { + { controller::LT, "/input/trigger/value" }, + { controller::LT, "/input/select/click" }, + { controller::LEFT_GRIP, "/input/squeeze/click" }, { controller::LX, "/input/trackpad/x" }, { controller::LY, "/input/trackpad/y" }, - { controller::LT, "/input/trigger/value" }, + { controller::LX, "/input/thumbstick/x" }, + { controller::LY, "/input/thumbstick/y" }, }, { + { controller::RT, "/input/trigger/value" }, + { controller::RT, "/input/select/click" }, + { controller::RIGHT_GRIP, "/input/squeeze/click" }, { controller::RX, "/input/trackpad/x" }, { controller::RY, "/input/trackpad/y" }, - { controller::RT, "/input/trigger/value" }, + { controller::RX, "/input/thumbstick/x" }, + { controller::RY, "/input/thumbstick/y" }, }, }; for (uint32_t i = 0; i < HAND_COUNT; i++) { for (const auto& [channel, path] : axesToUpdate[i]) { - _axisStateMap[channel].value = _actions.at(path)->getFloat(i).currentState; + auto action = _actions.at(path)->getFloat(i); + if (action.isActive) { + _axisStateMap[channel].value = action.currentState; + } } } - std::map buttonsToUpdate[2] = { + std::vector> buttonsToUpdate[2] = { { { controller::LS, "/input/trackpad/click" }, - { controller::LT_CLICK, "/input/trigger/click" }, { controller::LS_TOUCH, "/input/trackpad/touch" }, + { controller::LS, "/input/thumbstick/click" }, + { controller::LS_TOUCH, "/input/thumbstick/touch" }, + { controller::LT_CLICK, "/input/trigger/click" }, + { controller::LEFT_PRIMARY_THUMB, "/input/a/click" }, + { controller::LEFT_PRIMARY_THUMB, "/input/system/click" }, + { controller::LEFT_SECONDARY_THUMB, "/input/b/click" }, { controller::LEFT_SECONDARY_THUMB, "/input/menu/click" }, }, { { controller::RS, "/input/trackpad/click" }, - { controller::RT_CLICK, "/input/trigger/click" }, { controller::RS_TOUCH, "/input/trackpad/touch" }, + { controller::RS, "/input/thumbstick/click" }, + { controller::RS_TOUCH, "/input/thumbstick/touch" }, + { controller::RT_CLICK, "/input/trigger/click" }, + { controller::RIGHT_PRIMARY_THUMB, "/input/a/click" }, + { controller::RIGHT_PRIMARY_THUMB, "/input/system/click" }, + { controller::RIGHT_SECONDARY_THUMB, "/input/b/click" }, { controller::RIGHT_SECONDARY_THUMB, "/input/menu/click" }, }, }; for (uint32_t i = 0; i < HAND_COUNT; i++) { for (const auto& [channel, path] : buttonsToUpdate[i]) { - if (_actions.at(path)->getBool(i).currentState == XR_TRUE) { + auto action = _actions.at(path)->getBool(i); + if (action.isActive && action.currentState) { _buttonPressedMap.insert(channel); } } } + // TODO: better alternative to having every controller emulate a vive one partitionTouchpad(controller::LS, controller::LX, controller::LY, controller::LS_CENTER, controller::LS_X, controller::LS_Y); partitionTouchpad(controller::RS, controller::RX, controller::RY, controller::RS_CENTER, controller::RS_X, controller::RS_Y); - - // hack to convert the boolean squeeze buttons into axes - _axisStateMap[controller::LEFT_GRIP].value = _actions.at("/input/squeeze/click")->getBool(0).currentState ? 1.0f : 0.0f; - _axisStateMap[controller::RIGHT_GRIP].value = _actions.at("/input/squeeze/click")->getBool(1).currentState ? 1.0f : 0.0f; } // copied from openvr/ViveControllerManager From cedc5be52638c413e921f5f94b947c09efee8325 Mon Sep 17 00:00:00 2001 From: Ada Date: Thu, 12 Dec 2024 06:15:27 +1000 Subject: [PATCH 28/43] OpenXrInputPlugin: User-friendly input actions Replaces the raw controller button inputs with named OpenXR actions. There's a lot of engine components that expect raw controller inputs like the VR teleport script. Those will have to be refactored later, but for now this works well enough and is perfectly usable. A small issue I've hit is the LY input working for the teleport script, but not for smooth locomotion. I've hacked around this by having the "walk" action bound both to LX/LY and to the Translate actions. It's a bit janky for teleports, but it's functional. TODO: Feedback on intuitive bindings for other controller types besides just the HTC Vive controllers. --- interface/resources/controllers/openxr.json | 29 ++ interface/src/Application.cpp | 9 +- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 6 + plugins/openxr/src/OpenXrContext.cpp | 33 +- plugins/openxr/src/OpenXrContext.h | 8 +- plugins/openxr/src/OpenXrInputPlugin.cpp | 525 +++++++++++--------- plugins/openxr/src/OpenXrInputPlugin.h | 24 +- 7 files changed, 374 insertions(+), 260 deletions(-) create mode 100644 interface/resources/controllers/openxr.json diff --git a/interface/resources/controllers/openxr.json b/interface/resources/controllers/openxr.json new file mode 100644 index 0000000000..6f1c982ffd --- /dev/null +++ b/interface/resources/controllers/openxr.json @@ -0,0 +1,29 @@ +{ + "name": "OpenXR Actions", + "channels": [ + { "from": "OpenXR.LeftHand", "to": "Standard.LeftHand" }, + { "from": "OpenXR.RightHand", "to": "Standard.RightHand" }, + { "from": "OpenXR.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] }, + + { "from": "OpenXR.LeftInteract", "to": "Standard.LT", "filters": [{"type": "deadZone", "min": 0.05}]}, + { "from": "OpenXR.RightInteract", "to": "Standard.RT", "filters": [{"type": "deadZone", "min": 0.05}]}, + { "from": "OpenXR.LeftInteractClick", "to": "Standard.LTClick" }, + { "from": "OpenXR.RightInteractClick", "to": "Standard.RTClick" }, + + { "from": "OpenXR.LeftGrip", "to": "Standard.LeftGrip" }, + { "from": "OpenXR.RightGrip", "to": "Standard.RightGrip" }, + + { "from": "OpenXR.WalkX", "to": "Actions.TranslateX" }, + { "from": "OpenXR.WalkY", "to": "Actions.TranslateZ" }, + { "from": "OpenXR.WalkX", "peek": true, "to": "Standard.LX" }, + { "from": "OpenXR.WalkY", "peek": true, "to": "Standard.LY" }, + + { "from": "OpenXR.Turn", "to": "Standard.RX"}, + { "from": "OpenXR.Teleport", "to": "Standard.RY" }, + + { "from": "OpenXR.CycleCamera", "to": "Actions.CycleCamera" }, + { "from": "OpenXR.Sprint", "to": "Actions.Sprint" }, + { "from": "OpenXR.ToggleTablet", "to": "Standard.LeftSecondaryThumb" }, + { "from": "OpenXR.Jump", "to": "Standard.RightSecondaryThumb" } + ] +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c434b4b7f6..20ac5360fd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2373,12 +2373,6 @@ void Application::update(float deltaTime) { AnimDebugDraw::getInstance().update(); } - // a hack to prevent the engine from trying - // to pump out hundreds and hundreds of simulation - // ticks per second that can't be displayed - std::this_thread::sleep_for(5ms); - - { // Game loop is done, mark the end of the frame for the scene transactions and the render loop to take over PerformanceTimer perfTimer("enqueueFrame"); getMain3DScene()->enqueueFrame(); @@ -2393,6 +2387,9 @@ void Application::update(float deltaTime) { if (getActiveDisplayPlugin()->isHmd()) { PerformanceTimer perfTimer("squeezeVision"); _visionSqueeze.updateVisionSqueeze(myAvatar->getSensorToWorldMatrix(), deltaTime); + + // FIXME HACK: OpenXR doesn't limit the game rate for some reason and wastes cpu time + std::this_thread::sleep_for(5ms); } } diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index b5c1f713e7..2e275e7dff 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -469,6 +469,7 @@ bool OpenVrDisplayPlugin::internalActivate() { vr::VRCompositor()->ForceInterleavedReprojectionOn(true); } +#if 0 // set up default sensor space such that the UI overlay will align with the front of the room. auto chaperone = vr::VRChaperone(); if (chaperone) { @@ -485,6 +486,7 @@ bool OpenVrDisplayPlugin::internalActivate() { qDebug() << "OpenVR: error could not get chaperone pointer"; #endif } +#endif if (_threadedSubmit) { _submitThread = std::make_shared(*this); @@ -775,6 +777,9 @@ QString OpenVrDisplayPlugin::getPreferredAudioOutDevice() const { } QRectF OpenVrDisplayPlugin::getPlayAreaRect() { +#if 1 + return QRectF(); +#else auto chaperone = vr::VRChaperone(); if (!chaperone) { qWarning() << "No chaperone"; @@ -806,6 +811,7 @@ QRectF OpenVrDisplayPlugin::getPlayAreaRect() { glm::vec2 dimensions = glm::vec2(maxXZ.x - minXZ.x, maxXZ.z - minXZ.z); return QRectF(center.x, center.y, dimensions.x, dimensions.y); +#endif } DisplayPlugin::StencilMaskMeshOperator OpenVrDisplayPlugin::getStencilMaskMeshOperator() { diff --git a/plugins/openxr/src/OpenXrContext.cpp b/plugins/openxr/src/OpenXrContext.cpp index 2664b49bcf..91336b193c 100644 --- a/plugins/openxr/src/OpenXrContext.cpp +++ b/plugins/openxr/src/OpenXrContext.cpp @@ -82,6 +82,7 @@ bool OpenXrContext::initInstance() { return false; bool openglSupported = false; + bool bindingModificationSupported = false; qCInfo(xr_context_cat, "Runtime supports %d extensions:", count); for (uint32_t i = 0; i < count; i++) { @@ -89,6 +90,18 @@ bool OpenXrContext::initInstance() { if (strcmp(XR_KHR_OPENGL_ENABLE_EXTENSION_NAME, properties[i].extensionName) == 0) { openglSupported = true; } + + if (strcmp(XR_KHR_BINDING_MODIFICATION_EXTENSION_NAME, properties[i].extensionName) == 0) { + bindingModificationSupported = true; + } + + if (strcmp(XR_EXT_DPAD_BINDING_EXTENSION_NAME, properties[i].extensionName) == 0) { + _dpadBindingSupported = true; + } + + if (strcmp(XR_EXT_PALM_POSE_EXTENSION_NAME, properties[i].extensionName) == 0) { + _palmPoseSupported = true; + } } if (!openglSupported) { @@ -96,14 +109,21 @@ bool OpenXrContext::initInstance() { return false; } - std::vector enabled = { XR_KHR_OPENGL_ENABLE_EXTENSION_NAME }; + std::vector enabled = {XR_KHR_OPENGL_ENABLE_EXTENSION_NAME}; + if (bindingModificationSupported && _dpadBindingSupported) { + enabled.emplace(enabled.end(), XR_KHR_BINDING_MODIFICATION_EXTENSION_NAME); + enabled.emplace(enabled.end(), XR_EXT_DPAD_BINDING_EXTENSION_NAME); + } + if (_palmPoseSupported) { + enabled.emplace(enabled.end(), XR_EXT_PALM_POSE_EXTENSION_NAME); + } XrInstanceCreateInfo info = { .type = XR_TYPE_INSTANCE_CREATE_INFO, .applicationInfo = { - .applicationName = "overte", + .applicationName = "Overte", .applicationVersion = 1, - .engineName = "overte", + .engineName = "Overte", .engineVersion = 0, .apiVersion = XR_CURRENT_API_VERSION, }, @@ -126,6 +146,8 @@ bool OpenXrContext::initInstance() { xrStringToPath(_instance, "/user/hand/left", &_handPaths[0]); xrStringToPath(_instance, "/user/hand/right", &_handPaths[1]); + + xrStringToPath(_instance, "/interaction_profiles/htc/vive_controller", &_viveControllerPath); return true; } @@ -342,6 +364,11 @@ bool OpenXrContext::pollEvents() { if (!xrCheck(_instance, res, "Failed to get interaction profile")) continue; + _dpadNeedsClick = false; + if (state.interactionProfile == _viveControllerPath) { + _dpadNeedsClick = true; + } + uint32_t bufferCountOutput; char profilePath[XR_MAX_PATH_LENGTH]; res = xrPathToString(_instance, state.interactionProfile, XR_MAX_PATH_LENGTH, &bufferCountOutput, diff --git a/plugins/openxr/src/OpenXrContext.h b/plugins/openxr/src/OpenXrContext.h index 82baf098f5..225e5509fe 100644 --- a/plugins/openxr/src/OpenXrContext.h +++ b/plugins/openxr/src/OpenXrContext.h @@ -69,9 +69,15 @@ public: QString _systemName; bool _isSessionRunning = false; + bool _dpadBindingSupported = false; + bool _palmPoseSupported = false; + bool _dpadNeedsClick = false; + private: XrSessionState _lastSessionState = XR_SESSION_STATE_UNKNOWN; + XrPath _viveControllerPath = XR_NULL_PATH; + public: OpenXrContext(); ~OpenXrContext(); @@ -101,4 +107,4 @@ inline static glm::quat xrQuatToGlm(const XrQuaternionf& q) { return glm::quat(q.w, q.x, q.y, q.z); } -bool xrCheck(XrInstance instance, XrResult result, const char* message); \ No newline at end of file +bool xrCheck(XrInstance instance, XrResult result, const char* message); diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index 0ebe70f439..405fcb9f34 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -102,7 +102,7 @@ void OpenXrInputPlugin::loadSettings() { void OpenXrInputPlugin::saveSettings() const { } -OpenXrInputPlugin::InputDevice::InputDevice(std::shared_ptr c) : controller::InputDevice("Vive") { +OpenXrInputPlugin::InputDevice::InputDevice(std::shared_ptr c) : controller::InputDevice("OpenXR") { _context = c; } @@ -126,8 +126,10 @@ bool OpenXrInputPlugin::InputDevice::triggerHapticPulse(float strength, float du nanoseconds durationNs = duration_cast(milliseconds(static_cast(duration * 10.0f))); XrDuration xrDuration = durationNs.count(); - if (!_actions.at("/output/haptic")->applyHaptic(index, xrDuration, XR_FREQUENCY_UNSPECIFIED, 0.5f * strength)) { - qCCritical(xr_input_cat, "Failed to apply haptic feedback!"); + auto path = (index == 0) ? "hand_haptic_left" : "hand_haptic_right"; + + if (!_actions.at(path)->applyHaptic(xrDuration, XR_FREQUENCY_UNSPECIFIED, 0.5f * strength)) { + qCCritical(xr_input_cat) << "Failed to apply haptic feedback!"; } return true; @@ -142,12 +144,8 @@ bool OpenXrInputPlugin::Action::init(XrActionSet actionSet) { .subactionPaths = _context->_handPaths, }; - QString name = QString::fromStdString(_path); - name.replace("/input/", ""); - name.replace("/", "-"); - strcpy(info.actionName, name.toUtf8().data()); - name.replace("-", " "); - strcpy(info.localizedActionName, name.toUtf8().data()); + strncpy(info.actionName, _id.c_str(), XR_MAX_ACTION_NAME_SIZE - 1); + strncpy(info.localizedActionName, _friendlyName.c_str(), XR_MAX_LOCALIZED_ACTION_NAME_SIZE - 1); XrResult result = xrCreateAction(actionSet, &info, &_action); if (!xrCheck(instance, result, "Failed to create action")) @@ -163,23 +161,20 @@ bool OpenXrInputPlugin::Action::init(XrActionSet actionSet) { return true; } -const std::vector HAND_PATHS = { "left", "right" }; - std::vector OpenXrInputPlugin::Action::getBindings() { assert(_action != XR_NULL_HANDLE); std::vector bindings; for (uint32_t i = 0; i < HAND_COUNT; i++) { XrPath path; - std::string pathString = "/user/hand/" + HAND_PATHS[i] + _path; - xrStringToPath(_context->_instance, pathString.c_str(), &path); + xrStringToPath(_context->_instance, _id.c_str(), &path); XrActionSuggestedBinding binding = { .action = _action, .binding = path }; bindings.push_back(binding); } return bindings; } -XrActionStateFloat OpenXrInputPlugin::Action::getFloat(uint32_t handId) { +XrActionStateFloat OpenXrInputPlugin::Action::getFloat() { XrActionStateFloat state = { .type = XR_TYPE_ACTION_STATE_FLOAT, }; @@ -187,7 +182,6 @@ XrActionStateFloat OpenXrInputPlugin::Action::getFloat(uint32_t handId) { XrActionStateGetInfo info = { .type = XR_TYPE_ACTION_STATE_GET_INFO, .action = _action, - .subactionPath = _context->_handPaths[handId], }; XrResult result = xrGetActionStateFloat(_context->_session, &info, &state); @@ -196,7 +190,23 @@ XrActionStateFloat OpenXrInputPlugin::Action::getFloat(uint32_t handId) { return state; } -XrActionStateBoolean OpenXrInputPlugin::Action::getBool(uint32_t handId) { +XrActionStateVector2f OpenXrInputPlugin::Action::getVector2f() { + XrActionStateVector2f state = { + .type = XR_TYPE_ACTION_STATE_VECTOR2F, + }; + + XrActionStateGetInfo info = { + .type = XR_TYPE_ACTION_STATE_GET_INFO, + .action = _action, + }; + + XrResult result = xrGetActionStateVector2f(_context->_session, &info, &state); + xrCheck(_context->_instance, result, "Failed to get vector2 state!"); + + return state; +} + +XrActionStateBoolean OpenXrInputPlugin::Action::getBool() { XrActionStateBoolean state = { .type = XR_TYPE_ACTION_STATE_BOOLEAN, }; @@ -204,7 +214,6 @@ XrActionStateBoolean OpenXrInputPlugin::Action::getBool(uint32_t handId) { XrActionStateGetInfo info = { .type = XR_TYPE_ACTION_STATE_GET_INFO, .action = _action, - .subactionPath = _context->_handPaths[handId], }; XrResult result = xrGetActionStateBoolean(_context->_session, &info, &state); @@ -213,14 +222,13 @@ XrActionStateBoolean OpenXrInputPlugin::Action::getBool(uint32_t handId) { return state; } -XrSpaceLocation OpenXrInputPlugin::Action::getPose(uint32_t handId) { +XrSpaceLocation OpenXrInputPlugin::Action::getPose() { XrActionStatePose state = { .type = XR_TYPE_ACTION_STATE_POSE, }; XrActionStateGetInfo info = { .type = XR_TYPE_ACTION_STATE_GET_INFO, .action = _action, - .subactionPath = _context->_handPaths[handId], }; XrResult result = xrGetActionStatePose(_context->_session, &info, &state); @@ -231,14 +239,14 @@ XrSpaceLocation OpenXrInputPlugin::Action::getPose(uint32_t handId) { }; if (_context->_lastPredictedDisplayTime.has_value()) { - result = xrLocateSpace(_poseSpaces[handId], _context->_stageSpace, _context->_lastPredictedDisplayTime.value(), &location); + result = xrLocateSpace(_poseSpace, _context->_stageSpace, _context->_lastPredictedDisplayTime.value(), &location); xrCheck(_context->_instance, result, "Failed to locate hand space!"); } return location; } -bool OpenXrInputPlugin::Action::applyHaptic(uint32_t handId, XrDuration duration, float frequency, float amplitude) { +bool OpenXrInputPlugin::Action::applyHaptic(XrDuration duration, float frequency, float amplitude) { XrHapticVibration vibration = { .type = XR_TYPE_HAPTIC_VIBRATION, .duration = duration, @@ -249,7 +257,6 @@ bool OpenXrInputPlugin::Action::applyHaptic(uint32_t handId, XrDuration duration XrHapticActionInfo haptic_action_info = { .type = XR_TYPE_HAPTIC_ACTION_INFO, .action = _action, - .subactionPath = _context->_handPaths[handId], }; XrResult result = xrApplyHapticFeedback(_context->_session, &haptic_action_info, (const XrHapticBaseHeader*)&vibration); @@ -259,44 +266,40 @@ bool OpenXrInputPlugin::Action::applyHaptic(uint32_t handId, XrDuration duration bool OpenXrInputPlugin::Action::createPoseSpaces() { assert(_action != XR_NULL_HANDLE); - for (int hand = 0; hand < HAND_COUNT; hand++) { - XrActionSpaceCreateInfo info = { - .type = XR_TYPE_ACTION_SPACE_CREATE_INFO, - .action = _action, - .subactionPath = _context->_handPaths[hand], - .poseInActionSpace = XR_INDENTITY_POSE, - }; + XrActionSpaceCreateInfo info = { + .type = XR_TYPE_ACTION_SPACE_CREATE_INFO, + .action = _action, + .poseInActionSpace = XR_INDENTITY_POSE, + }; - XrResult result = xrCreateActionSpace(_context->_session, &info, &_poseSpaces[hand]); - if (!xrCheck(_context->_instance, result, "Failed to create hand pose space")) - return false; - } + XrResult result = xrCreateActionSpace(_context->_session, &info, &_poseSpace); + if (!xrCheck(_context->_instance, result, "Failed to create hand pose space")) + return false; return true; } bool OpenXrInputPlugin::InputDevice::initBindings(const std::string& profileName, - const std::vector& actionsToBind) { + const std::map& actionsToBind) { XrPath profilePath; XrResult result = xrStringToPath(_context->_instance, profileName.c_str(), &profilePath); if (!xrCheck(_context->_instance, result, "Failed to get interaction profile")) return false; - std::vector bindings; - for (const std::string& path : actionsToBind) { - if (!_actions.contains(path)) { - qCWarning(xr_input_cat, "%s has unbound input %s", profileName.c_str(), path.c_str()); - continue; - } - std::vector actionBindings = _actions.at(path)->getBindings(); - bindings.insert(std::end(bindings), std::begin(actionBindings), std::end(actionBindings)); + std::vector suggestions; + for (const auto& [actionName, inputPathRaw] : actionsToBind) { + XrActionSuggestedBinding bind = { + .action = _actions[actionName]->_action, + }; + xrStringToPath(_context->_instance, inputPathRaw.c_str(), &bind.binding); + suggestions.emplace(suggestions.end(), bind); } const XrInteractionProfileSuggestedBinding suggestedBinding = { .type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING, .interactionProfile = profilePath, - .countSuggestedBindings = (uint32_t)bindings.size(), - .suggestedBindings = bindings.data(), + .countSuggestedBindings = (uint32_t)suggestions.size(), + .suggestedBindings = suggestions.data(), }; result = xrSuggestInteractionProfileBindings(_context->_instance, &suggestedBinding); @@ -307,60 +310,41 @@ bool OpenXrInputPlugin::InputDevice::initBindings(const std::string& profileName controller::Input::NamedVector OpenXrInputPlugin::InputDevice::getAvailableInputs() const { using namespace controller; - // clang-format off QVector availableInputs{ makePair(HEAD, "Head"), makePair(LEFT_HAND, "LeftHand"), makePair(RIGHT_HAND, "RightHand"), - // Trackpad analogs - makePair(LX, "LX"), - makePair(LY, "LY"), - makePair(RX, "RX"), - makePair(RY, "RY"), + // INPUT FIXME: Actions.Translate.{X,Z} work + // perfectly but Standard.LY is unreliable + makePair(LX, "WalkX"), + makePair(LY, "WalkY"), - // capacitive touch on the touch pad - makePair(LS_TOUCH, "LSTouch"), - makePair(RS_TOUCH, "RSTouch"), + makePair(LT, "LeftInteract"), + makePair(RT, "RightInteract"), + makePair(LT_CLICK, "LeftInteractClick"), + makePair(RT_CLICK, "RightInteractClick"), - // touch pad press - makePair(LS, "LS"), - makePair(RS, "RS"), - // Differentiate where we are in the touch pad click - makePair(LS_CENTER, "LSCenter"), - makePair(LS_X, "LSX"), - makePair(LS_Y, "LSY"), - makePair(RS_CENTER, "RSCenter"), - makePair(RS_X, "RSX"), - makePair(RS_Y, "RSY"), - - // triggers - makePair(LT, "LT"), - makePair(RT, "RT"), - // Trigger clicks - makePair(LT_CLICK, "LTClick"), - makePair(RT_CLICK, "RTClick"), - - // low profile side grip button. makePair(LEFT_GRIP, "LeftGrip"), makePair(RIGHT_GRIP, "RightGrip"), - makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"), - makePair(RIGHT_PRIMARY_THUMB, "RightPrimaryThumb"), - makePair(LEFT_SECONDARY_THUMB, "LeftSecondaryThumb"), - makePair(RIGHT_SECONDARY_THUMB, "RightSecondaryThumb"), - - makePair(LEFT_SECONDARY_THUMB, "LeftApplicationMenu"), - makePair(RIGHT_SECONDARY_THUMB, "RightApplicationMenu"), + // INPUT TODO: horrific hack that breaks depending on handedness + // because the input system is in dire need of a refactor, + // it was (mostly) designed with raw inputs in mind which makes + // it extremely difficult to map onto openxr actions + makePair(LEFT_PRIMARY_THUMB, "ToggleTablet"), + makePair(RIGHT_PRIMARY_THUMB, "Jump"), + makePair(DD, "Sprint"), + makePair(RX, "Turn"), + makePair(RY, "Teleport"), + makePair(LS_TOUCH, "CycleCamera"), }; - // clang-format on return availableInputs; } QString OpenXrInputPlugin::InputDevice::getDefaultMappingConfig() const { - // FIXME: for some reason vive works but openxr_generic breaks the vertical trackpad? - return PathUtils::resourcesPath() + "/controllers/vive.json"; + return PathUtils::resourcesPath() + "/controllers/openxr.json"; } bool OpenXrInputPlugin::InputDevice::initActions() { @@ -373,107 +357,96 @@ bool OpenXrInputPlugin::InputDevice::initActions() { XrActionSetCreateInfo actionSetInfo = { .type = XR_TYPE_ACTION_SET_CREATE_INFO, - .actionSetName = "action_set", - .localizedActionSetName = "Action Set", + .actionSetName = "overte", + .localizedActionSetName = "Overte", .priority = 0, }; XrResult result = xrCreateActionSet(instance, &actionSetInfo, &_actionSet); if (!xrCheck(instance, result, "Failed to create action set.")) return false; - // clang-format off - std::map actionsToInit = { - { "/output/haptic", XR_ACTION_TYPE_VIBRATION_OUTPUT }, - { "/input/grip/pose", XR_ACTION_TYPE_POSE_INPUT }, + std::map> actionTypes = { + {"tablet", {"Toggle Tablet", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"teleport", {"Teleport", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"cycle_camera", {"Cycle Camera", XR_ACTION_TYPE_BOOLEAN_INPUT}}, - // click but pretend it's a float for the trigger actions - { "/input/select/click", XR_ACTION_TYPE_FLOAT_INPUT }, - { "/input/menu/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/system/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + {"interact_left", {"Left Interact", XR_ACTION_TYPE_FLOAT_INPUT}}, + {"interact_right", {"Right Interact", XR_ACTION_TYPE_FLOAT_INPUT}}, - { "/input/trackpad/x", XR_ACTION_TYPE_FLOAT_INPUT }, - { "/input/trackpad/y", XR_ACTION_TYPE_FLOAT_INPUT }, - { "/input/trackpad/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/trackpad/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, + {"grip_left", {"Left Grip", XR_ACTION_TYPE_FLOAT_INPUT}}, + {"grip_right", {"Right Grip", XR_ACTION_TYPE_FLOAT_INPUT}}, - { "/input/thumbstick/x", XR_ACTION_TYPE_FLOAT_INPUT }, - { "/input/thumbstick/y", XR_ACTION_TYPE_FLOAT_INPUT }, - { "/input/thumbstick/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/thumbstick/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/a/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/b/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/a/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/b/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, + {"turn_left", {"Turn Left", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"turn_right", {"Turn Right", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"walk", {"Walk", XR_ACTION_TYPE_VECTOR2F_INPUT}}, + {"sprint", {"Sprint", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"jump", {"Jump", XR_ACTION_TYPE_BOOLEAN_INPUT}}, - { "/input/squeeze/click", XR_ACTION_TYPE_FLOAT_INPUT }, - { "/input/trigger/value", XR_ACTION_TYPE_FLOAT_INPUT }, - { "/input/trigger/click", XR_ACTION_TYPE_BOOLEAN_INPUT }, - { "/input/trigger/touch", XR_ACTION_TYPE_BOOLEAN_INPUT }, + // in case the runtime doesn't support dpad emulation + {"stick_left", {"Left Stick (Fallback)", XR_ACTION_TYPE_VECTOR2F_INPUT}}, + {"stick_right", {"Right Stick (Fallback)", XR_ACTION_TYPE_VECTOR2F_INPUT}}, + {"stick_click_left", {"Left Stick Click (Fallback)", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"stick_click_right", {"Right Stick Click (Fallback)", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + + {"hand_pose_left", {"Left Hand Pose", XR_ACTION_TYPE_POSE_INPUT}}, + {"hand_pose_right", {"Right Hand Pose", XR_ACTION_TYPE_POSE_INPUT}}, + {"hand_haptic_left", {"Left Hand Haptic", XR_ACTION_TYPE_VIBRATION_OUTPUT}}, + {"hand_haptic_right", {"Right Hand Haptic", XR_ACTION_TYPE_VIBRATION_OUTPUT}}, }; - // clang-format on - for (const auto& [path, type] : actionsToInit) { - std::shared_ptr action = std::make_shared(_context, type, path); + // palm pose is nice but monado doesn't support it yet + auto hand_pose_name = (_context->_palmPoseSupported) ? "/palm_ext/pose" : "/grip/pose"; + + // TODO: set up the openxr dpad bindings modifier (looks complicated) + std::map> actionSuggestions = { + {"/interaction_profiles/khr/simple_controller", { + {"tablet", "/user/hand/left/input/menu/click"}, + {"teleport", "/user/hand/right/input/menu/click"}, + {"interact_left", "/user/hand/left/input/select/click"}, + {"interact_right", "/user/hand/right/input/select/click"}, + {"hand_pose_left", std::string("/user/hand/left/input") + hand_pose_name}, + {"hand_pose_right", std::string("/user/hand/right/input") + hand_pose_name}, + {"hand_haptic_left", "/user/hand/left/output/haptic"}, + {"hand_haptic_right", "/user/hand/right/output/haptic"}, + }}, + {"/interaction_profiles/htc/vive_controller", { + {"tablet", "/user/hand/left/input/menu/click"}, + //{"teleport", "/user/hand/right/input/trackpad/dpad_up"}, + //{"cycle_camera", "/user/hand/right/input/trackpad/dpad_down"}, + {"interact_left", "/user/hand/left/input/trigger/value"}, + {"interact_right", "/user/hand/right/input/trigger/value"}, + {"grip_left", "/user/hand/left/input/squeeze/click"}, + {"grip_right", "/user/hand/right/input/squeeze/click"}, + {"jump", "/user/hand/right/input/menu/click"}, + {"walk", "/user/hand/left/input/trackpad"}, + //{"turn_left", "/user/hand/right/input/trackpad/dpad_left"}, + //{"turn_right", "/user/hand/right/input/trackpad/dpad_right"}, + {"stick_left", "/user/hand/left/input/trackpad"}, + {"stick_right", "/user/hand/right/input/trackpad"}, + {"stick_click_left", "/user/hand/left/input/trackpad/click"}, + {"stick_click_right", "/user/hand/right/input/trackpad/click"}, + {"hand_pose_left", std::string("/user/hand/left/input") + hand_pose_name}, + {"hand_pose_right", std::string("/user/hand/right/input") + hand_pose_name}, + {"hand_haptic_left", "/user/hand/left/output/haptic"}, + {"hand_haptic_right", "/user/hand/right/output/haptic"}, + }}, + }; + + for (const auto& [id, args] : actionTypes) { + auto friendlyName = args.first; + auto xr_type = args.second; + std::shared_ptr action = std::make_shared(_context, id, friendlyName, xr_type); if (!action->init(_actionSet)) { - qCCritical(xr_input_cat, "Creating action %s failed!", path.c_str()); + qCCritical(xr_input_cat) << "Creating action " << id.c_str() << " failed!"; } else { - _actions.emplace(path, action); + _actions.emplace(id, action); } } - // Khronos Simple Controller - std::vector simpleBindings = { - "/input/select/click", - "/input/menu/click", - "/input/grip/pose", - "/output/haptic", - }; - - if (!initBindings("/interaction_profiles/khr/simple_controller", simpleBindings)) { - qCCritical(xr_input_cat, "Failed to init bindings for khr/simple_controller"); - } - - // HTC Vive - std::vector viveBindings = { - "/input/system/click", - "/input/squeeze/click", - "/input/menu/click", - "/input/trigger/click", - "/input/trigger/value", - "/input/trackpad/x", - "/input/trackpad/y", - "/input/trackpad/click", - "/input/trackpad/touch", - "/input/grip/pose", - "/output/haptic", - }; - - if (!initBindings("/interaction_profiles/htc/vive_controller", viveBindings)) { - qCCritical(xr_input_cat, "Failed to init bindings for htc/vive_controller"); - } - - // Valve Index Controller - // clang-format off - std::vector indexBindings = { - "/input/grip/pose", - "/input/thumbstick/x", - "/input/thumbstick/y", - "/input/thumbstick/touch", - "/input/thumbstick/click", - "/input/a/click", - "/input/a/touch", - "/input/b/click", - "/input/b/touch", - "/input/trigger/value", - "/input/trigger/click", - "/input/trigger/touch", - "/output/haptic", - "/input/system/click", - }; - // clang-format on - - if (!initBindings("/interaction_profiles/valve/index_controller", indexBindings)) { - qCCritical(xr_input_cat, "Failed to init bindings for valve/index_controller"); + for (const auto& [profile, input] : actionSuggestions) { + if (!initBindings(profile, input)) { + qCWarning(xr_input_cat) << "Failed to suggest actions for " << profile.c_str(); + } } XrSessionActionSetsAttachInfo attachInfo = { @@ -499,8 +472,8 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I return; } - if (!initActions()) { - qCCritical(xr_input_cat, "Could not initialize actions!"); + if (!_actionsInitialized && !initActions()) { + qCCritical(xr_input_cat) << "Could not initialize actions!"; return; } @@ -534,7 +507,8 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand; for (int i = 0; i < HAND_COUNT; i++) { - XrSpaceLocation handLocation = _actions.at("/input/grip/pose")->getPose(i); + auto hand_path = (i == 0) ? "hand_pose_left" : "hand_pose_right"; + XrSpaceLocation handLocation = _actions.at(hand_path)->getPose(); bool locationValid = (handLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0; if (locationValid) { vec3 translation = xrVecToGlm(handLocation.pose.position); @@ -556,87 +530,160 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I glm::mat4 defaultHeadOffset = createMatFromQuatAndPos(-DEFAULT_AVATAR_HEAD_ROT, -DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET); _poseStateMap[controller::HEAD] = _context->_lastHeadPose.postTransform(defaultHeadOffset).transform(sensorToAvatar); - std::vector> axesToUpdate[2] = { - { - { controller::LT, "/input/trigger/value" }, - { controller::LT, "/input/select/click" }, - { controller::LEFT_GRIP, "/input/squeeze/click" }, - { controller::LX, "/input/trackpad/x" }, - { controller::LY, "/input/trackpad/y" }, - { controller::LX, "/input/thumbstick/x" }, - { controller::LY, "/input/thumbstick/y" }, - }, - { - { controller::RT, "/input/trigger/value" }, - { controller::RT, "/input/select/click" }, - { controller::RIGHT_GRIP, "/input/squeeze/click" }, - { controller::RX, "/input/trackpad/x" }, - { controller::RY, "/input/trackpad/y" }, - { controller::RX, "/input/thumbstick/x" }, - { controller::RY, "/input/thumbstick/y" }, - }, + std::vector> floatsToUpdate = { + {"interact_left", controller::LT}, + {"interact_right", controller::RT}, + {"grip_left", controller::LEFT_GRIP}, + {"grip_right", controller::RIGHT_GRIP}, }; - for (uint32_t i = 0; i < HAND_COUNT; i++) { - for (const auto& [channel, path] : axesToUpdate[i]) { - auto action = _actions.at(path)->getFloat(i); - if (action.isActive) { - _axisStateMap[channel].value = action.currentState; - } + for (const auto& [name, channel] : floatsToUpdate) { + auto action = _actions.at(name)->getFloat(); + if (action.isActive) { + _axisStateMap[channel].value = action.currentState; } } - std::vector> buttonsToUpdate[2] = { - { - { controller::LS, "/input/trackpad/click" }, - { controller::LS_TOUCH, "/input/trackpad/touch" }, - { controller::LS, "/input/thumbstick/click" }, - { controller::LS_TOUCH, "/input/thumbstick/touch" }, - { controller::LT_CLICK, "/input/trigger/click" }, - { controller::LEFT_PRIMARY_THUMB, "/input/a/click" }, - { controller::LEFT_PRIMARY_THUMB, "/input/system/click" }, - { controller::LEFT_SECONDARY_THUMB, "/input/b/click" }, - { controller::LEFT_SECONDARY_THUMB, "/input/menu/click" }, - }, - { - { controller::RS, "/input/trackpad/click" }, - { controller::RS_TOUCH, "/input/trackpad/touch" }, - { controller::RS, "/input/thumbstick/click" }, - { controller::RS_TOUCH, "/input/thumbstick/touch" }, - { controller::RT_CLICK, "/input/trigger/click" }, - { controller::RIGHT_PRIMARY_THUMB, "/input/a/click" }, - { controller::RIGHT_PRIMARY_THUMB, "/input/system/click" }, - { controller::RIGHT_SECONDARY_THUMB, "/input/b/click" }, - { controller::RIGHT_SECONDARY_THUMB, "/input/menu/click" }, - }, + std::vector> axesToUpdate = { + //{"stick_left", controller::LX, controller::LY}, + //{"stick_right", controller::RX, controller::RY}, + {"walk", controller::LX, controller::LY}, }; - for (uint32_t i = 0; i < HAND_COUNT; i++) { - for (const auto& [channel, path] : buttonsToUpdate[i]) { - auto action = _actions.at(path)->getBool(i); - if (action.isActive && action.currentState) { - _buttonPressedMap.insert(channel); - } + for (const auto& [name, x_channel, y_channel] : axesToUpdate) { + auto action = _actions.at(name)->getVector2f(); + if (action.isActive) { + _axisStateMap[x_channel].value = action.currentState.x; + _axisStateMap[y_channel].value = -action.currentState.y; } } - // TODO: better alternative to having every controller emulate a vive one - partitionTouchpad(controller::LS, controller::LX, controller::LY, controller::LS_CENTER, controller::LS_X, controller::LS_Y); - partitionTouchpad(controller::RS, controller::RX, controller::RY, controller::RS_CENTER, controller::RS_X, controller::RS_Y); + // INPUT TODO: more hacks + { + auto turn_left = _actions.at("turn_left")->getBool(); + if (turn_left.isActive && turn_left.currentState) { + _axisStateMap[controller::RX].value -= 1.0f; + } + + auto turn_right = _actions.at("turn_right")->getBool(); + if (turn_right.isActive && turn_right.currentState) { + _axisStateMap[controller::RX].value += 1.0f; + } + + // INPUT TODO: the teleport script is hardcoded to use the LY/RY axes + auto teleport = _actions.at("teleport")->getBool(); + if (teleport.isActive && teleport.currentState) { + _axisStateMap[controller::RY].value += 1.0f; + } + } + + // don't double up on the stick values between the proper actions and fallback sticks + _axisStateMap[controller::LX].value = std::clamp(_axisStateMap[controller::LX].value, -1.0f, 1.0f); + _axisStateMap[controller::LY].value = std::clamp(_axisStateMap[controller::LY].value, -1.0f, 1.0f); + _axisStateMap[controller::RX].value = std::clamp(_axisStateMap[controller::RX].value, -1.0f, 1.0f); + _axisStateMap[controller::RY].value = std::clamp(_axisStateMap[controller::RY].value, -1.0f, 1.0f); + + std::vector> buttonsToUpdate = { + {"tablet", controller::LEFT_PRIMARY_THUMB}, + {"jump", controller::RIGHT_PRIMARY_THUMB}, + {"cycle_camera", controller::LS_TOUCH}, + {"sprint", controller::DD}, + }; + + for (const auto& [name, channel] : buttonsToUpdate) { + auto action = _actions.at(name)->getBool(); + if (action.isActive && action.currentState) { + _buttonPressedMap.insert(channel); + } + } + + // INPUT TODO: it's really not necessary to expose "interact click" bindings, + // but the engine expects there to be click buttons to work properly + { + auto left = _actions.at("interact_left")->getFloat(); + if (left.isActive && left.currentState == 1.0f) { + _buttonPressedMap.insert(controller::LT_CLICK); + } + + auto right = _actions.at("interact_right")->getFloat(); + if (right.isActive && right.currentState == 1.0f) { + _buttonPressedMap.insert(controller::RT_CLICK); + } + } + + // emulate dpad if the dpad extension isn't available + emulateStickDPad(); } -// copied from openvr/ViveControllerManager -void OpenXrInputPlugin::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton) { - // Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values. - const float CENTER_DEADBAND = 0.6f; - const float DIAGONAL_DIVIDE_IN_RADIANS = PI / 4.0f; - if (_buttonPressedMap.find(sButton) != _buttonPressedMap.end()) { - float absX = abs(_axisStateMap[xAxis].value); - float absY = abs(_axisStateMap[yAxis].value); - glm::vec2 cartesianQuadrantI(absX, absY); - float angle = glm::atan(cartesianQuadrantI.y / cartesianQuadrantI.x); - float radius = glm::length(cartesianQuadrantI); - bool isCenter = radius < CENTER_DEADBAND; - _buttonPressedMap.insert(isCenter ? centerPseudoButton : ((angle < DIAGONAL_DIVIDE_IN_RADIANS) ? xPseudoButton :yPseudoButton)); +void OpenXrInputPlugin::InputDevice::emulateStickDPad() { + auto right_stick = _actions.at("stick_right")->getVector2f(); + + auto left_stick_click = _actions.at("stick_click_left")->getBool(); + auto right_stick_click = _actions.at("stick_click_right")->getBool(); + + auto left_needs_click = _context->_dpadNeedsClick && left_stick_click.isActive; + auto right_needs_click = _context->_dpadNeedsClick && left_stick_click.isActive; + + auto left_clicked = left_needs_click ? left_stick_click.currentState : true; + auto right_clicked = right_needs_click ? right_stick_click.currentState : true; + + if (right_stick.isActive && right_clicked) { + // camera switch (right stick down) + if ( + right_stick.currentState.y < -0.6f + && right_stick.currentState.x > -0.4f + && right_stick.currentState.x < 0.4f + ) { + _buttonPressedMap.insert(controller::LS_TOUCH); + } + + // snap turn left + if ( + right_stick.currentState.x < -0.6f + && right_stick.currentState.y > -0.4f + && right_stick.currentState.y < 0.4f + ) { + _axisStateMap[controller::RX].value = -1.0f; + _axisStateMap[controller::RY].value = 0.0f; + } + + // snap turn right + if ( + right_stick.currentState.x > 0.6f + && right_stick.currentState.y > -0.4f + && right_stick.currentState.y < 0.4f + ) { + _axisStateMap[controller::RX].value = 1.0f; + _axisStateMap[controller::RY].value = 0.0f; + } + + // teleport + if ( + right_stick.currentState.y > 0.6f + && right_stick.currentState.x > -0.4f + && right_stick.currentState.x < 0.4f + ) { + _axisStateMap[controller::RX].value = 0.0f; + _axisStateMap[controller::RY].value = -1.0f; + } + } + + // set stick inputs to zero if they're not + // clicked in or too close to the center + if ( + !right_clicked + || (right_stick.currentState.x > -0.6f + && right_stick.currentState.x < 0.6f + && right_stick.currentState.y > -0.6f + && right_stick.currentState.y < 0.6f) + ) { + _axisStateMap[controller::RX].value = 0.0f; + _axisStateMap[controller::RY].value = 0.0f; + } + + // don't walk unless the trackpad is clicked in + if (!left_clicked) { + _axisStateMap[controller::LX].value = 0.0f; + _axisStateMap[controller::LY].value = 0.0f; } } diff --git a/plugins/openxr/src/OpenXrInputPlugin.h b/plugins/openxr/src/OpenXrInputPlugin.h index 54e3011159..63614dbcf9 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.h +++ b/plugins/openxr/src/OpenXrInputPlugin.h @@ -47,26 +47,28 @@ public: private: class Action { public: - Action(std::shared_ptr c, XrActionType type, const std::string& path) { + Action(std::shared_ptr c, const std::string& id, const std::string &friendlyName, XrActionType type) { _context = c; - _path = path; + _id = id; + _friendlyName = friendlyName; _type = type; } bool init(XrActionSet actionSet); std::vector getBindings(); - XrActionStateFloat getFloat(uint32_t handId); - XrActionStateBoolean getBool(uint32_t handId); - XrSpaceLocation getPose(uint32_t handId); - bool applyHaptic(uint32_t handId, XrDuration duration, float frequency, float amplitude); + XrActionStateFloat getFloat(); + XrActionStateVector2f getVector2f(); + XrActionStateBoolean getBool(); + XrSpaceLocation getPose(); + bool applyHaptic(XrDuration duration, float frequency, float amplitude); + XrAction _action = XR_NULL_HANDLE; private: bool createPoseSpaces(); - XrAction _action = XR_NULL_HANDLE; std::shared_ptr _context; - std::string _path; + std::string _id, _friendlyName; XrActionType _type; - XrSpace _poseSpaces[HAND_COUNT] = { XR_NULL_HANDLE, XR_NULL_HANDLE }; + XrSpace _poseSpace = XR_NULL_HANDLE; }; class InputDevice : public controller::InputDevice { @@ -80,7 +82,7 @@ private: void focusOutEvent() override; bool triggerHapticPulse(float strength, float duration, uint16_t index) override; - void partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton); + void emulateStickDPad(); mutable std::recursive_mutex _lock; template @@ -98,7 +100,7 @@ private: bool _actionsInitialized = false; bool initActions(); - bool initBindings(const std::string& profileName, const std::vector& actionsToBind); + bool initBindings(const std::string& profileName, const std::map& actionsToBind); }; bool _registeredWithInputMapper = false; From 524665f54c1530adc5010b7c5278b477f8341228 Mon Sep 17 00:00:00 2001 From: Ada Date: Thu, 12 Dec 2024 11:27:22 +1000 Subject: [PATCH 29/43] Revert OpenVrDisplayPlugin getPlayAreaRect hack --- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 2e275e7dff..b5c1f713e7 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -469,7 +469,6 @@ bool OpenVrDisplayPlugin::internalActivate() { vr::VRCompositor()->ForceInterleavedReprojectionOn(true); } -#if 0 // set up default sensor space such that the UI overlay will align with the front of the room. auto chaperone = vr::VRChaperone(); if (chaperone) { @@ -486,7 +485,6 @@ bool OpenVrDisplayPlugin::internalActivate() { qDebug() << "OpenVR: error could not get chaperone pointer"; #endif } -#endif if (_threadedSubmit) { _submitThread = std::make_shared(*this); @@ -777,9 +775,6 @@ QString OpenVrDisplayPlugin::getPreferredAudioOutDevice() const { } QRectF OpenVrDisplayPlugin::getPlayAreaRect() { -#if 1 - return QRectF(); -#else auto chaperone = vr::VRChaperone(); if (!chaperone) { qWarning() << "No chaperone"; @@ -811,7 +806,6 @@ QRectF OpenVrDisplayPlugin::getPlayAreaRect() { glm::vec2 dimensions = glm::vec2(maxXZ.x - minXZ.x, maxXZ.z - minXZ.z); return QRectF(center.x, center.y, dimensions.x, dimensions.y); -#endif } DisplayPlugin::StencilMaskMeshOperator OpenVrDisplayPlugin::getStencilMaskMeshOperator() { From d21dee44fbba5c2c66613c2e6bade51b2c799c92 Mon Sep 17 00:00:00 2001 From: Ada Date: Thu, 12 Dec 2024 12:14:11 +1000 Subject: [PATCH 30/43] OpenXrInputPlugin: Oculus touch and WMR controller profiles --- plugins/openxr/src/OpenXrInputPlugin.cpp | 41 ++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index 405fcb9f34..838cf3bada 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -395,7 +395,10 @@ bool OpenXrInputPlugin::InputDevice::initActions() { }; // palm pose is nice but monado doesn't support it yet - auto hand_pose_name = (_context->_palmPoseSupported) ? "/palm_ext/pose" : "/grip/pose"; + // (disable it for now because i can't test what palm + // pose looks like and if it'll break something) + //auto hand_pose_name = (_context->_palmPoseSupported) ? "/palm_ext/pose" : "/grip/pose"; + auto hand_pose_name = "/grip/pose"; // TODO: set up the openxr dpad bindings modifier (looks complicated) std::map> actionSuggestions = { @@ -430,6 +433,40 @@ bool OpenXrInputPlugin::InputDevice::initActions() { {"hand_haptic_left", "/user/hand/left/output/haptic"}, {"hand_haptic_right", "/user/hand/right/output/haptic"}, }}, + {"/interaction_profiles/oculus/touch_controller", { + {"tablet", "/user/hand/left/input/y/click"}, + {"interact_left", "/user/hand/left/input/trigger/value"}, + {"interact_right", "/user/hand/right/input/trigger/value"}, + {"grip_left", "/user/hand/left/input/squeeze/value"}, + {"grip_right", "/user/hand/right/input/squeeze/value"}, + {"jump", "/user/hand/right/input/b/click"}, + {"walk", "/user/hand/left/input/thumbstick"}, + {"stick_left", "/user/hand/left/input/thumbstick"}, + {"stick_right", "/user/hand/right/input/thumbstick"}, + {"stick_click_left", "/user/hand/left/input/thumbstick/click"}, + {"stick_click_right", "/user/hand/right/input/thumbstick/click"}, + {"hand_pose_left", std::string("/user/hand/left/input") + hand_pose_name}, + {"hand_pose_right", std::string("/user/hand/right/input") + hand_pose_name}, + {"hand_haptic_left", "/user/hand/left/output/haptic"}, + {"hand_haptic_right", "/user/hand/right/output/haptic"}, + }}, + {"/interaction_profiles/microsoft/motion_controller", { + {"tablet", "/user/hand/left/input/menu/click"}, + {"interact_left", "/user/hand/left/input/trigger/value"}, + {"interact_right", "/user/hand/right/input/trigger/value"}, + {"grip_left", "/user/hand/left/input/squeeze/click"}, + {"grip_right", "/user/hand/right/input/squeeze/click"}, + {"jump", "/user/hand/right/input/menu/click"}, + {"walk", "/user/hand/left/input/thumbstick"}, + {"stick_left", "/user/hand/left/input/thumbstick"}, + {"stick_right", "/user/hand/right/input/thumbstick"}, + {"stick_click_left", "/user/hand/left/input/thumbstick/click"}, + {"stick_click_right", "/user/hand/right/input/thumbstick/click"}, + {"hand_pose_left", std::string("/user/hand/left/input") + hand_pose_name}, + {"hand_pose_right", std::string("/user/hand/right/input") + hand_pose_name}, + {"hand_haptic_left", "/user/hand/left/output/haptic"}, + {"hand_haptic_right", "/user/hand/right/output/haptic"}, + }}, }; for (const auto& [id, args] : actionTypes) { @@ -646,7 +683,7 @@ void OpenXrInputPlugin::InputDevice::emulateStickDPad() { _axisStateMap[controller::RX].value = -1.0f; _axisStateMap[controller::RY].value = 0.0f; } - + // snap turn right if ( right_stick.currentState.x > 0.6f From fedd4c8c1cf63cfdbb7a1f46d4b43bf91d6a9313 Mon Sep 17 00:00:00 2001 From: Ada Date: Thu, 12 Dec 2024 12:50:25 +1000 Subject: [PATCH 31/43] OpenXrContext: Don't try and fail to initialize on Wayland --- plugins/openxr/src/OpenXrContext.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/plugins/openxr/src/OpenXrContext.cpp b/plugins/openxr/src/OpenXrContext.cpp index 91336b193c..9fc1d282dd 100644 --- a/plugins/openxr/src/OpenXrContext.cpp +++ b/plugins/openxr/src/OpenXrContext.cpp @@ -9,6 +9,8 @@ #include "OpenXrContext.h" #include +#include +#include #include @@ -59,6 +61,11 @@ 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."); + return false; + } + uint32_t count = 0; XrResult result = xrEnumerateInstanceExtensionProperties(nullptr, 0, &count, nullptr); @@ -90,15 +97,15 @@ bool OpenXrContext::initInstance() { if (strcmp(XR_KHR_OPENGL_ENABLE_EXTENSION_NAME, properties[i].extensionName) == 0) { openglSupported = true; } - + if (strcmp(XR_KHR_BINDING_MODIFICATION_EXTENSION_NAME, properties[i].extensionName) == 0) { bindingModificationSupported = true; } - + if (strcmp(XR_EXT_DPAD_BINDING_EXTENSION_NAME, properties[i].extensionName) == 0) { _dpadBindingSupported = true; } - + if (strcmp(XR_EXT_PALM_POSE_EXTENSION_NAME, properties[i].extensionName) == 0) { _palmPoseSupported = true; } @@ -146,7 +153,7 @@ bool OpenXrContext::initInstance() { xrStringToPath(_instance, "/user/hand/left", &_handPaths[0]); xrStringToPath(_instance, "/user/hand/right", &_handPaths[1]); - + xrStringToPath(_instance, "/interaction_profiles/htc/vive_controller", &_viveControllerPath); return true; From 167c9c433f9d16019d97039e9d7eb1a96cbb341a Mon Sep 17 00:00:00 2001 From: Ada Date: Thu, 2 Jan 2025 17:44:41 +1000 Subject: [PATCH 32/43] OpenXrInputPlugin: Adjust hand and head-to-eyes offsets Hands are now basically perfect (on Vive controllers, at least, palm pose/grip surface would really help but it's not well supported) Head now bends properly at the bottom of the head bone and the HMD view isn't stuck at the bottom of the head bone. TODO: The current solution doesn't account for avatar scale or the actual positions of their eyes. The pull-forward offset for the view might also be too far, I'll need to get feedback on it. --- plugins/openxr/src/OpenXrInputPlugin.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index 838cf3bada..cd5fdac5c4 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -446,7 +446,7 @@ bool OpenXrInputPlugin::InputDevice::initActions() { {"stick_click_left", "/user/hand/left/input/thumbstick/click"}, {"stick_click_right", "/user/hand/right/input/thumbstick/click"}, {"hand_pose_left", std::string("/user/hand/left/input") + hand_pose_name}, - {"hand_pose_right", std::string("/user/hand/right/input") + hand_pose_name}, + {"hand_pose_right", std::string("/user/hand/right/input") + hand_pose_name}, {"hand_haptic_left", "/user/hand/left/output/haptic"}, {"hand_haptic_right", "/user/hand/right/output/haptic"}, }}, @@ -463,7 +463,7 @@ bool OpenXrInputPlugin::InputDevice::initActions() { {"stick_click_left", "/user/hand/left/input/thumbstick/click"}, {"stick_click_right", "/user/hand/right/input/thumbstick/click"}, {"hand_pose_left", std::string("/user/hand/left/input") + hand_pose_name}, - {"hand_pose_right", std::string("/user/hand/right/input") + hand_pose_name}, + {"hand_pose_right", std::string("/user/hand/right/input") + hand_pose_name}, {"hand_haptic_left", "/user/hand/left/output/haptic"}, {"hand_haptic_right", "/user/hand/right/output/haptic"}, }}, @@ -557,15 +557,26 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I // (apparently the openvr plugin uses similar constants?) glm::mat4 posOffset(1.0f); posOffset *= glm::translate(glm::vec3(handOffset[0]) * (i == 0 ? 0.04f : -0.04f)); - posOffset *= glm::translate(glm::vec3(handOffset[1]) * -0.15f); - posOffset *= glm::translate(glm::vec3(handOffset[2]) * 0.02f); + posOffset *= glm::translate(glm::vec3(handOffset[1]) * -0.16f); + posOffset *= glm::translate(glm::vec3(handOffset[2]) * -0.04f); _poseStateMap[i == 0 ? controller::LEFT_HAND : controller::RIGHT_HAND] = pose.postTransform(posOffset).postTransform(handOffset).transform(sensorToAvatar); } } - glm::mat4 defaultHeadOffset = createMatFromQuatAndPos(-DEFAULT_AVATAR_HEAD_ROT, -DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET); - _poseStateMap[controller::HEAD] = _context->_lastHeadPose.postTransform(defaultHeadOffset).transform(sensorToAvatar); + glm::mat4 defaultHeadOffset; + if (inputCalibrationData.hmdAvatarAlignmentType == controller::HmdAvatarAlignmentType::Eyes) { + // align the eyes of the user with the eyes of the avatar + defaultHeadOffset = (glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat) * Matrices::Y_180; + } else { + defaultHeadOffset = createMatFromQuatAndPos(-DEFAULT_AVATAR_HEAD_ROT, -DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET); + } + // try to account for weirdness with HMD view being inside the root of the head bone + // TODO: is the 15cm(?) forward shift too much? + // FIXME: this doesn't account for avatar scale + auto headCorrectionA = glm::translate(glm::vec3(0.0f, 0.15f, 0.15f)); + auto headCorrectionB = glm::translate(glm::vec3(0.0f, -0.2f, 0.0f)); + _poseStateMap[controller::HEAD] = _context->_lastHeadPose.postTransform(headCorrectionA).postTransform(defaultHeadOffset).transform(sensorToAvatar).postTransform(headCorrectionB); std::vector> floatsToUpdate = { {"interact_left", controller::LT}, From 7c3fa5b1751ff0cee8249d70a3cd1d1aaf6e32b6 Mon Sep 17 00:00:00 2001 From: Ada Date: Sat, 25 Jan 2025 16:03:04 +1000 Subject: [PATCH 33/43] OpenXrInputPlugin: Correct HMD-head bone offset --- plugins/openxr/src/OpenXrInputPlugin.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index cd5fdac5c4..cfd9a6d899 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -554,7 +554,7 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I glm::mat4 handOffset = i == 0 ? glm::toMat4(leftRotationOffset) : glm::toMat4(rightRotationOffset); // offset constants taken from OpenComposite - // (apparently the openvr plugin uses similar constants?) + // and tweaked to fit my hands as best i could glm::mat4 posOffset(1.0f); posOffset *= glm::translate(glm::vec3(handOffset[0]) * (i == 0 ? 0.04f : -0.04f)); posOffset *= glm::translate(glm::vec3(handOffset[1]) * -0.16f); @@ -565,18 +565,21 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I } glm::mat4 defaultHeadOffset; + float eyeZOffset = 0.16f; if (inputCalibrationData.hmdAvatarAlignmentType == controller::HmdAvatarAlignmentType::Eyes) { // align the eyes of the user with the eyes of the avatar - defaultHeadOffset = (glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat) * Matrices::Y_180; + defaultHeadOffset = Matrices::Y_180 * (glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat); + + // dont double up on eye offset + eyeZOffset = 0.0f; } else { defaultHeadOffset = createMatFromQuatAndPos(-DEFAULT_AVATAR_HEAD_ROT, -DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET); } + // try to account for weirdness with HMD view being inside the root of the head bone - // TODO: is the 15cm(?) forward shift too much? - // FIXME: this doesn't account for avatar scale - auto headCorrectionA = glm::translate(glm::vec3(0.0f, 0.15f, 0.15f)); + auto headCorrectionA = glm::translate(glm::vec3(0.0f, 0.16f, eyeZOffset)); auto headCorrectionB = glm::translate(glm::vec3(0.0f, -0.2f, 0.0f)); - _poseStateMap[controller::HEAD] = _context->_lastHeadPose.postTransform(headCorrectionA).postTransform(defaultHeadOffset).transform(sensorToAvatar).postTransform(headCorrectionB); + _poseStateMap[controller::HEAD] = _context->_lastHeadPose.postTransform(headCorrectionA).postTransform(defaultHeadOffset).postTransform(headCorrectionB).transform(sensorToAvatar); std::vector> floatsToUpdate = { {"interact_left", controller::LT}, From 36768b6b88068d092603ecdc77d7b39f3a5feef3 Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 26 Jan 2025 00:35:17 +1000 Subject: [PATCH 34/43] DeferredFrameTransform: Hack to get lighting working properly in VR Somewhere, the eye view transform is being applied twice. For now, this hack with applying an inverse view transform corrects the bugged lighting in the XR branch. --- plugins/openxr/src/OpenXrContext.cpp | 20 -------------------- plugins/openxr/src/OpenXrContext.h | 2 -- 2 files changed, 22 deletions(-) diff --git a/plugins/openxr/src/OpenXrContext.cpp b/plugins/openxr/src/OpenXrContext.cpp index 9fc1d282dd..e29649f0a4 100644 --- a/plugins/openxr/src/OpenXrContext.cpp +++ b/plugins/openxr/src/OpenXrContext.cpp @@ -89,7 +89,6 @@ bool OpenXrContext::initInstance() { return false; bool openglSupported = false; - bool bindingModificationSupported = false; qCInfo(xr_context_cat, "Runtime supports %d extensions:", count); for (uint32_t i = 0; i < count; i++) { @@ -97,18 +96,6 @@ bool OpenXrContext::initInstance() { if (strcmp(XR_KHR_OPENGL_ENABLE_EXTENSION_NAME, properties[i].extensionName) == 0) { openglSupported = true; } - - if (strcmp(XR_KHR_BINDING_MODIFICATION_EXTENSION_NAME, properties[i].extensionName) == 0) { - bindingModificationSupported = true; - } - - if (strcmp(XR_EXT_DPAD_BINDING_EXTENSION_NAME, properties[i].extensionName) == 0) { - _dpadBindingSupported = true; - } - - if (strcmp(XR_EXT_PALM_POSE_EXTENSION_NAME, properties[i].extensionName) == 0) { - _palmPoseSupported = true; - } } if (!openglSupported) { @@ -117,13 +104,6 @@ bool OpenXrContext::initInstance() { } std::vector enabled = {XR_KHR_OPENGL_ENABLE_EXTENSION_NAME}; - if (bindingModificationSupported && _dpadBindingSupported) { - enabled.emplace(enabled.end(), XR_KHR_BINDING_MODIFICATION_EXTENSION_NAME); - enabled.emplace(enabled.end(), XR_EXT_DPAD_BINDING_EXTENSION_NAME); - } - if (_palmPoseSupported) { - enabled.emplace(enabled.end(), XR_EXT_PALM_POSE_EXTENSION_NAME); - } XrInstanceCreateInfo info = { .type = XR_TYPE_INSTANCE_CREATE_INFO, diff --git a/plugins/openxr/src/OpenXrContext.h b/plugins/openxr/src/OpenXrContext.h index 225e5509fe..025682a101 100644 --- a/plugins/openxr/src/OpenXrContext.h +++ b/plugins/openxr/src/OpenXrContext.h @@ -69,8 +69,6 @@ public: QString _systemName; bool _isSessionRunning = false; - bool _dpadBindingSupported = false; - bool _palmPoseSupported = false; bool _dpadNeedsClick = false; private: From 1b95f97457c5a9bd0e49377d70f88a085c969619 Mon Sep 17 00:00:00 2001 From: Ada Date: Tue, 11 Feb 2025 04:06:30 +1000 Subject: [PATCH 35/43] OpenXrInputPlugin: Tweak hand offset slightly --- plugins/openxr/src/OpenXrInputPlugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index cfd9a6d899..bdf98bd2c3 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -556,9 +556,9 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I // offset constants taken from OpenComposite // and tweaked to fit my hands as best i could glm::mat4 posOffset(1.0f); - posOffset *= glm::translate(glm::vec3(handOffset[0]) * (i == 0 ? 0.04f : -0.04f)); + posOffset *= glm::translate(glm::vec3(handOffset[0]) * (i == 0 ? 0.1f : -0.1f)); posOffset *= glm::translate(glm::vec3(handOffset[1]) * -0.16f); - posOffset *= glm::translate(glm::vec3(handOffset[2]) * -0.04f); + posOffset *= glm::translate(glm::vec3(handOffset[2]) * -0.02f); _poseStateMap[i == 0 ? controller::LEFT_HAND : controller::RIGHT_HAND] = pose.postTransform(posOffset).postTransform(handOffset).transform(sensorToAvatar); } From 84055beba14ea2cc5f8687fec7b8d3f3e98ac600 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Fri, 7 Mar 2025 22:21:53 +0100 Subject: [PATCH 36/43] Add missing namespace --- interface/src/Application.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 20ac5360fd..c0cb875ba2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2388,7 +2388,10 @@ void Application::update(float deltaTime) { PerformanceTimer perfTimer("squeezeVision"); _visionSqueeze.updateVisionSqueeze(myAvatar->getSensorToWorldMatrix(), deltaTime); + // XRTODO: won't this impact performance, especially on slower CPUs? + // I think it will also affect OpenVR // FIXME HACK: OpenXR doesn't limit the game rate for some reason and wastes cpu time + using namespace std::chrono_literals; std::this_thread::sleep_for(5ms); } } From fafbede81902d86fe219d00c9b5c4154393d9ff7 Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 14 Mar 2025 07:14:29 +1000 Subject: [PATCH 37/43] Remove unused and outdated Index bindings --- .../resources/controllers/openxr_index.json | 35 ------------------- 1 file changed, 35 deletions(-) delete mode 100644 interface/resources/controllers/openxr_index.json diff --git a/interface/resources/controllers/openxr_index.json b/interface/resources/controllers/openxr_index.json deleted file mode 100644 index 44e1ae0a97..0000000000 --- a/interface/resources/controllers/openxr_index.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "OpenXR Index to Actions", - "channels": [ - { "from": "Index.LeftHand", "to": "Standard.LeftHand" }, - { "from": "Index.RightHand", "to": "Standard.RightHand" }, - { "from": "Index.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] }, - - { "from": "Index.LeftPrimaryThumb", "to": "Actions.Down" }, - { "from": "Index.LeftPrimaryThumbTouch", "to": "Standard.LeftPrimaryThumbTouch" }, - { "from": "Index.LeftSecondaryThumb", "to": "Actions.ContextMenu" }, - { "from": "Index.LeftSecondaryThumbTouch", "to": "Standard.LeftSecondaryThumbTouch" }, - - { "from": "Index.RightPrimaryThumb", "to": "Actions.Up" }, - { "from": "Index.RightPrimaryThumbTouch", "to": "Standard.RightPrimaryThumbTouch" }, - { "from": "Index.RightSecondaryThumb", "to": "Actions.Sprint" }, - { "from": "Index.RightSecondaryThumbTouch", "to": "Standard.RightSecondaryThumbTouch" }, - - { "from": "Index.LY", "to": "Actions.TranslateZ", "filters": ["invert"] }, - { "from": "Index.LX", "to": "Actions.TranslateX" }, - { "from": "Index.RY", "to": "Standard.RY" }, - { "from": "Index.RX", "to": "Standard.RX" }, - { "from": "Index.LS", "to": "Standard.LS" }, - { "from": "Index.RS", "to": "Actions.CycleCamera" }, - { "from": "Index.LSTouch", "to": "Standard.LSTouch" }, - { "from": "Index.RSTouch", "to": "Standard.RSTouch" }, - - { "from": "Index.RT", "to": "Standard.RT" }, - { "from": "Index.LT", "to": "Standard.LT" }, - { "from": "Index.RTClick", "to": "Standard.RTClick" }, - { "from": "Index.LTClick", "to": "Standard.LTClick" }, - - { "from": "Index.LeftPrimaryIndexTouch", "to": "Standard.LeftPrimaryIndexTouch" }, - { "from": "Index.RightPrimaryIndexTouch", "to": "Standard.RightPrimaryIndexTouch" } - ] -} From d09a920ad2e6adb8da40d4510b187bab1dff6c4f Mon Sep 17 00:00:00 2001 From: Ada Date: Tue, 1 Apr 2025 23:33:49 +1000 Subject: [PATCH 38/43] Update OpenXR-SDK to 1.1.46 --- cmake/ports/openxr-loader/fix-jinja2.patch | 23 ------------------- .../fix-openxr-sdk-jsoncpp.patch | 16 ++++++------- cmake/ports/openxr-loader/portfile.cmake | 23 ++----------------- .../python3_8_compatibility.patch | 13 ----------- cmake/ports/openxr-loader/vcpkg.json | 2 +- 5 files changed, 11 insertions(+), 66 deletions(-) delete mode 100644 cmake/ports/openxr-loader/fix-jinja2.patch delete mode 100644 cmake/ports/openxr-loader/python3_8_compatibility.patch diff --git a/cmake/ports/openxr-loader/fix-jinja2.patch b/cmake/ports/openxr-loader/fix-jinja2.patch deleted file mode 100644 index 5d77cb4e46..0000000000 --- a/cmake/ports/openxr-loader/fix-jinja2.patch +++ /dev/null @@ -1,23 +0,0 @@ -From d80c7dc3f4810fc49e4444590d39ef71e8a9b01c Mon Sep 17 00:00:00 2001 -From: Adam Johnson -Date: Sat, 19 Feb 2022 19:42:31 -0500 -Subject: [PATCH] Fix bad import in jinja2 - ---- - external/python/jinja2/utils.py | 6 +++++- - 1 file changed, 5 insertions(+), 1 deletion(-) - -diff --git a/external/python/jinja2/utils.py b/external/python/jinja2/utils.py -index db9c5d06..f198e3ef 100644 ---- a/external/python/jinja2/utils.py -+++ b/external/python/jinja2/utils.py -@@ -639,4 +639,8 @@ def __repr__(self): - - - # Imported here because that's where it was in the past --from markupsafe import Markup, escape, soft_unicode -+from markupsafe import Markup, escape -+try: -+ from markupsafe import soft_unicode -+except ImportError: -+ from markupsafe import soft_str as soft_unicode diff --git a/cmake/ports/openxr-loader/fix-openxr-sdk-jsoncpp.patch b/cmake/ports/openxr-loader/fix-openxr-sdk-jsoncpp.patch index 758d55e0f8..cfe1ef2b80 100644 --- a/cmake/ports/openxr-loader/fix-openxr-sdk-jsoncpp.patch +++ b/cmake/ports/openxr-loader/fix-openxr-sdk-jsoncpp.patch @@ -1,8 +1,8 @@ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index c75b145..386494c 100644 +index c2e53cf..2c195de 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt -@@ -89,7 +89,7 @@ if(NOT VULKAN_INCOMPATIBLE) +@@ -122,7 +122,7 @@ if(NOT METAL_INCOMPATIBLE) endif() find_package(Threads REQUIRED) @@ -12,12 +12,12 @@ index c75b145..386494c 100644 ### All options defined here option(BUILD_LOADER "Build loader" ON) diff --git a/src/loader/CMakeLists.txt b/src/loader/CMakeLists.txt -index 6a88cf4..0821a3d 100644 +index 28aff53..6ee58f4 100644 --- a/src/loader/CMakeLists.txt +++ b/src/loader/CMakeLists.txt -@@ -68,7 +68,11 @@ add_library(openxr_loader ${LIBRARY_TYPE} - ${openxr_loader_RESOURCE_FILE} - ) +@@ -101,7 +101,11 @@ endif() + + # Get jsoncpp externally or internally if(BUILD_WITH_SYSTEM_JSONCPP) - target_link_libraries(openxr_loader PRIVATE JsonCpp::JsonCpp) + if(BUILD_SHARED_LIBS) @@ -26,5 +26,5 @@ index 6a88cf4..0821a3d 100644 + target_link_libraries(openxr_loader PRIVATE jsoncpp_static) + endif() else() - target_sources(openxr_loader - PRIVATE + if(NOT BUILD_LOADER_WITH_EXCEPTION_HANDLING) + target_compile_definitions(openxr_loader PRIVATE JSON_USE_EXCEPTION=0) diff --git a/cmake/ports/openxr-loader/portfile.cmake b/cmake/ports/openxr-loader/portfile.cmake index 4d1127e56e..fb5b51296d 100644 --- a/cmake/ports/openxr-loader/portfile.cmake +++ b/cmake/ports/openxr-loader/portfile.cmake @@ -3,7 +3,7 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO KhronosGroup/OpenXR-SDK REF "release-${VERSION}" - SHA512 6efc7596e707f95366dbcdbac9bd7d0c20735a2175b4edf56a9e8a112cf0ab8b664069fe942313164a37119032ddbf5671bc88ab5f276005dd36e4a4dabba1c7 + SHA512 f5f02857036d14c3894bee979bf108c4066ff5551393bc9bdde85dced5c5007148880c6174174dfe3b844e00baeb66106afbf18be069958128404d6a9bdc96ce HEAD_REF master PATCHES fix-openxr-sdk-jsoncpp.patch @@ -13,21 +13,10 @@ vcpkg_from_github( OUT_SOURCE_PATH SDK_SOURCE_PATH REPO KhronosGroup/OpenXR-SDK-Source REF "release-${VERSION}" - SHA512 04bdb0f16078209b5edd175a3396f70e1ceb8cfa382c65b8fda388e565480e3844daf68e0d987e72ed8c21d3148af0b41a2170911ec1660565887e0e5ae6d2bf + SHA512 29155f5cd6104a479ce25ea090020001a01652ce42823ddad3e2569d7d2d513a0339c084d90acd3a00b220f7ba1cf68af1ac4b4c01f0a949aa9d919a1914d6c9 HEAD_REF master PATCHES fix-openxr-sdk-jsoncpp.patch - fix-jinja2.patch -) - -vcpkg_from_github( - OUT_SOURCE_PATH HPP_SOURCE_PATH - REPO KhronosGroup/OpenXR-hpp - REF 63db9919822f8af6f7bf7416ba6a015d4617202e - SHA512 9e768f485d1631f8e74f35f028a64e2d64e33d362c53ae1c54427a10786e3befdd24089927319aa1a4b4c3e010247bd6cb3394bcee460c467c637ab6bc7bec90 - HEAD_REF master - PATCHES - python3_8_compatibility.patch ) # Weird behavior inside the OpenXR loader. On Windows they force shared libraries to use static crt, and @@ -57,14 +46,6 @@ vcpkg_cmake_configure( vcpkg_cmake_install() -# Generate the OpenXR C++ bindings -set(ENV{OPENXR_REPO} "${SDK_SOURCE_PATH}") -vcpkg_execute_required_process( - COMMAND ${PYTHON3} "${HPP_SOURCE_PATH}/scripts/hpp_genxr.py" -quiet -registry "${SDK_SOURCE_PATH}/specification/registry/xr.xml" -o "${CURRENT_PACKAGES_DIR}/include/openxr" - WORKING_DIRECTORY "${HPP_SOURCE_PATH}" - LOGNAME "openxr-hpp" -) - if(VCPKG_TARGET_IS_WINDOWS) vcpkg_cmake_config_fixup(PACKAGE_NAME OpenXR CONFIG_PATH cmake) else() diff --git a/cmake/ports/openxr-loader/python3_8_compatibility.patch b/cmake/ports/openxr-loader/python3_8_compatibility.patch deleted file mode 100644 index 657bb2b7ab..0000000000 --- a/cmake/ports/openxr-loader/python3_8_compatibility.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/scripts/hpp_genxr.py b/scripts/hpp_genxr.py -index ce419b0..23e1d3d 100644 ---- a/scripts/hpp_genxr.py -+++ b/scripts/hpp_genxr.py -@@ -36,7 +36,7 @@ from xrconventions import OpenXRConventions - from data import EXCLUDED_EXTENSIONS - - --def makeREstring(strings: Iterable[str], default: typing.Optional[str] = None) -> str: -+def makeREstring(strings, default: typing.Optional[str] = None) -> str: - """Turn a list of strings into a regexp string matching exactly those strings.""" - if strings or default is None: - return f"^({'|'.join(re.escape(s) for s in strings)})$" diff --git a/cmake/ports/openxr-loader/vcpkg.json b/cmake/ports/openxr-loader/vcpkg.json index a45e3c9199..bc9767db20 100644 --- a/cmake/ports/openxr-loader/vcpkg.json +++ b/cmake/ports/openxr-loader/vcpkg.json @@ -1,6 +1,6 @@ { "name": "openxr-loader", - "version": "1.0.31", + "version": "1.1.46", "description": "A royalty-free, open standard that provides high-performance access to Augmented Reality (AR) and Virtual Reality (VR)—collectively known as XR—platforms and devices", "homepage": "https://github.com/KhronosGroup/OpenXR-SDK", "license": "Apache-2.0", From 9cf3c748ca51e51e26f6f3ce54355a62ca203a5f Mon Sep 17 00:00:00 2001 From: Ada Date: Wed, 2 Apr 2025 02:41:08 +1000 Subject: [PATCH 39/43] Input cleanup, game rate fix --- interface/resources/controllers/openxr.json | 40 +- interface/src/Application.cpp | 6 - plugins/openxr/src/OpenXrContext.cpp | 52 ++- plugins/openxr/src/OpenXrContext.h | 10 +- plugins/openxr/src/OpenXrDisplayPlugin.cpp | 32 +- plugins/openxr/src/OpenXrDisplayPlugin.h | 5 + plugins/openxr/src/OpenXrInputPlugin.cpp | 419 +++++++++----------- plugins/openxr/src/OpenXrInputPlugin.h | 3 +- 8 files changed, 295 insertions(+), 272 deletions(-) diff --git a/interface/resources/controllers/openxr.json b/interface/resources/controllers/openxr.json index 6f1c982ffd..b6b80808cd 100644 --- a/interface/resources/controllers/openxr.json +++ b/interface/resources/controllers/openxr.json @@ -1,29 +1,35 @@ { - "name": "OpenXR Actions", + "name": "OpenXR to Standard", "channels": [ { "from": "OpenXR.LeftHand", "to": "Standard.LeftHand" }, { "from": "OpenXR.RightHand", "to": "Standard.RightHand" }, { "from": "OpenXR.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] }, - { "from": "OpenXR.LeftInteract", "to": "Standard.LT", "filters": [{"type": "deadZone", "min": 0.05}]}, - { "from": "OpenXR.RightInteract", "to": "Standard.RT", "filters": [{"type": "deadZone", "min": 0.05}]}, - { "from": "OpenXR.LeftInteractClick", "to": "Standard.LTClick" }, - { "from": "OpenXR.RightInteractClick", "to": "Standard.RTClick" }, + { "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" }, - { "from": "OpenXR.RightGrip", "to": "Standard.RightGrip" }, + { "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.WalkX", "to": "Actions.TranslateX" }, - { "from": "OpenXR.WalkY", "to": "Actions.TranslateZ" }, - { "from": "OpenXR.WalkX", "peek": true, "to": "Standard.LX" }, - { "from": "OpenXR.WalkY", "peek": true, "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.Turn", "to": "Standard.RX"}, - { "from": "OpenXR.Teleport", "to": "Standard.RY" }, + { "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.CycleCamera", "to": "Actions.CycleCamera" }, - { "from": "OpenXR.Sprint", "to": "Actions.Sprint" }, - { "from": "OpenXR.ToggleTablet", "to": "Standard.LeftSecondaryThumb" }, - { "from": "OpenXR.Jump", "to": "Standard.RightSecondaryThumb" } + { "from": "OpenXR.RX", "to": "Standard.RX"}, + { "from": "OpenXR.RY", "to": "Standard.RY" }, + + { "from": "OpenXR.LS", "to": "Standard.LS" }, + { "from": "OpenXR.RS", "to": "Standard.RS" }, + { "from": "OpenXR.LSTouch", "to": "Standard.LSTouch" }, + { "from": "OpenXR.RSTouch", "to": "Standard.RSTouch" }, + + { "from": "OpenXR.LeftPrimaryThumb", "to": "Standard.LeftPrimaryThumb" }, + { "from": "OpenXR.RightPrimaryThumb", "to": "Standard.RightPrimaryThumb" }, + { "from": "OpenXR.LeftSecondaryThumb", "to": "Standard.LeftSecondaryThumb" }, + { "from": "OpenXR.RightSecondaryThumb", "to": "Standard.RightSecondaryThumb" } ] } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c0cb875ba2..4449acd6ad 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2387,12 +2387,6 @@ void Application::update(float deltaTime) { if (getActiveDisplayPlugin()->isHmd()) { PerformanceTimer perfTimer("squeezeVision"); _visionSqueeze.updateVisionSqueeze(myAvatar->getSensorToWorldMatrix(), deltaTime); - - // XRTODO: won't this impact performance, especially on slower CPUs? - // I think it will also affect OpenVR - // FIXME HACK: OpenXR doesn't limit the game rate for some reason and wastes cpu time - using namespace std::chrono_literals; - std::this_thread::sleep_for(5ms); } } diff --git a/plugins/openxr/src/OpenXrContext.cpp b/plugins/openxr/src/OpenXrContext.cpp index e29649f0a4..269486b313 100644 --- a/plugins/openxr/src/OpenXrContext.cpp +++ b/plugins/openxr/src/OpenXrContext.cpp @@ -36,10 +36,16 @@ bool xrCheck(XrInstance instance, XrResult result, const char* message) { // Extension functions must be loaded with xrGetInstanceProcAddr static PFN_xrGetOpenGLGraphicsRequirementsKHR pfnGetOpenGLGraphicsRequirementsKHR = nullptr; -static bool initFunctionPointers(XrInstance instance) { - XrResult result = xrGetInstanceProcAddr(instance, "xrGetOpenGLGraphicsRequirementsKHR", - (PFN_xrVoidFunction*)&pfnGetOpenGLGraphicsRequirementsKHR); - return xrCheck(instance, result, "Failed to get OpenGL graphics requirements function!"); + +static bool loadXrFunction(XrInstance instance, const char* name, PFN_xrVoidFunction* out) { + auto result = xrGetInstanceProcAddr(instance, name, out); + + if (result != XR_SUCCESS) { + qCCritical(xr_context_cat) << "Failed to load OpenXR function '" << name << "'"; + return false; + } + + return true; } OpenXrContext::OpenXrContext() { @@ -89,12 +95,15 @@ bool OpenXrContext::initInstance() { return false; bool openglSupported = false; + bool userPresenceSupported = false; qCInfo(xr_context_cat, "Runtime supports %d extensions:", count); for (uint32_t i = 0; i < count; i++) { qCInfo(xr_context_cat, "%s v%d", properties[i].extensionName, properties[i].extensionVersion); if (strcmp(XR_KHR_OPENGL_ENABLE_EXTENSION_NAME, properties[i].extensionName) == 0) { openglSupported = true; + } else if (strcmp(XR_EXT_USER_PRESENCE_EXTENSION_NAME, properties[i].extensionName) == 0) { + userPresenceSupported = true; } } @@ -104,6 +113,9 @@ bool OpenXrContext::initInstance() { } std::vector enabled = {XR_KHR_OPENGL_ENABLE_EXTENSION_NAME}; + if (userPresenceSupported) { + enabled.push_back(XR_EXT_USER_PRESENCE_EXTENSION_NAME); + } XrInstanceCreateInfo info = { .type = XR_TYPE_INSTANCE_CREATE_INFO, @@ -128,14 +140,25 @@ bool OpenXrContext::initInstance() { if (!xrCheck(XR_NULL_HANDLE, result, "Failed to create OpenXR instance.")) return false; - if (!initFunctionPointers(_instance)) + if (!loadXrFunction(_instance, "xrGetOpenGLGraphicsRequirementsKHR", (PFN_xrVoidFunction*)&pfnGetOpenGLGraphicsRequirementsKHR)) { + qCCritical(xr_context_cat) << "Failed to get OpenGL graphics requirements function!"; return false; + } xrStringToPath(_instance, "/user/hand/left", &_handPaths[0]); xrStringToPath(_instance, "/user/hand/right", &_handPaths[1]); xrStringToPath(_instance, "/interaction_profiles/htc/vive_controller", &_viveControllerPath); + if (userPresenceSupported) { + XrSystemUserPresencePropertiesEXT presenceProps = {XR_TYPE_SYSTEM_USER_PRESENCE_PROPERTIES_EXT}; + XrSystemProperties sysProps = {XR_TYPE_SYSTEM_PROPERTIES, &presenceProps}; + result = xrGetSystemProperties(_instance, _systemId, &sysProps); + if (xrCheck(XR_NULL_HANDLE, result, "Couldn't get system properties")) { + _userPresenceAvailable = presenceProps.supportsUserPresence; + } + } + return true; } @@ -332,14 +355,14 @@ bool OpenXrContext::pollEvents() { while (result == XR_SUCCESS) { switch (event.type) { case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { - XrEventDataInstanceLossPending* instanceLossPending = (XrEventDataInstanceLossPending*)&event; - qCCritical(xr_context_cat, "Instance loss pending at %lu! Destroying instance.", instanceLossPending->lossTime); + const auto& instanceLossPending = *reinterpret_cast(&event); + qCCritical(xr_context_cat, "Instance loss pending at %lu! Destroying instance.", instanceLossPending.lossTime); _shouldQuit = true; continue; } case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { - XrEventDataSessionStateChanged* sessionStateChanged = (XrEventDataSessionStateChanged*)&event; - if (!updateSessionState(sessionStateChanged->state)) { + const auto& sessionStateChanged = *reinterpret_cast(&event); + if (!updateSessionState(sessionStateChanged.state)) { return false; } break; @@ -351,9 +374,9 @@ bool OpenXrContext::pollEvents() { if (!xrCheck(_instance, res, "Failed to get interaction profile")) continue; - _dpadNeedsClick = false; - if (state.interactionProfile == _viveControllerPath) { - _dpadNeedsClick = true; + _stickEmulation = false; + if (_viveControllerPath != XR_NULL_PATH && state.interactionProfile == _viveControllerPath) { + _stickEmulation = true; } uint32_t bufferCountOutput; @@ -367,6 +390,11 @@ bool OpenXrContext::pollEvents() { } break; } + case XR_TYPE_EVENT_DATA_USER_PRESENCE_CHANGED_EXT: { + const auto& eventdata = *reinterpret_cast(&event); + _hmdMounted = eventdata.isUserPresent; + break; + } default: qCWarning(xr_context_cat, "Unhandled event type %d", event.type); } diff --git a/plugins/openxr/src/OpenXrContext.h b/plugins/openxr/src/OpenXrContext.h index 025682a101..2acb5f5b5c 100644 --- a/plugins/openxr/src/OpenXrContext.h +++ b/plugins/openxr/src/OpenXrContext.h @@ -69,7 +69,15 @@ public: QString _systemName; bool _isSessionRunning = false; - bool _dpadNeedsClick = false; + // hack for vive controllers + bool _stickEmulation = false; + + // only supported by a few runtimes, but lets us + // emulate OpenVR's headset proximity sensor system + bool _userPresenceAvailable = false; + + // whether the headset is on, using XR_EXT_user_presence + bool _hmdMounted = true; private: XrSessionState _lastSessionState = XR_SESSION_STATE_UNKNOWN; diff --git a/plugins/openxr/src/OpenXrDisplayPlugin.cpp b/plugins/openxr/src/OpenXrDisplayPlugin.cpp index 29843bddeb..0137ac22fd 100644 --- a/plugins/openxr/src/OpenXrDisplayPlugin.cpp +++ b/plugins/openxr/src/OpenXrDisplayPlugin.cpp @@ -31,6 +31,8 @@ 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 { @@ -77,13 +79,14 @@ glm::mat4 OpenXrDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& basePr return fovToProjection(_views.value()[(eye == Left) ? 0 : 1].fov, frustum.getNearClip(), frustum.getFarClip()); } -// TODO: This apparently wasn't right in the OpenVR plugin, but this is what it basically did. +// TODO: interface/src/Application_Graphics.cpp:535 glm::mat4 OpenXrDisplayPlugin::getCullingProjection(const glm::mat4& baseProjection) const { return getEyeProjection(Left, baseProjection); } -// TODO: This should not be explicilty known by the application. -// Let's just render as fast as we can and OpenXR will dictate the pace. +// 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 { return std::numeric_limits::max(); } @@ -134,6 +137,7 @@ static std::string glFormatStr(GLenum source) { ENUM_TO_STR(GL_RGBA16); ENUM_TO_STR(GL_RGBA16F); ENUM_TO_STR(GL_SRGB8_ALPHA8); + ENUM_TO_STR(GL_RGB10_A2UI); default: return std::format("0x{:X}", source); } @@ -256,6 +260,15 @@ 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); } @@ -324,6 +337,8 @@ 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) { @@ -421,6 +436,14 @@ bool OpenXrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _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); } @@ -515,7 +538,6 @@ bool OpenXrDisplayPlugin::endFrame() { }; if ((_lastViewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) == 0) { - qCWarning(xr_display_cat, "Not submitting layers because orientation is invalid."); info.layerCount = 0; } @@ -535,7 +557,7 @@ void OpenXrDisplayPlugin::postPreview() { } bool OpenXrDisplayPlugin::isHmdMounted() const { - return true; + return _context->_hmdMounted; } void OpenXrDisplayPlugin::updatePresentPose() { diff --git a/plugins/openxr/src/OpenXrDisplayPlugin.h b/plugins/openxr/src/OpenXrDisplayPlugin.h index 01df5fcf27..aef27c3cb2 100644 --- a/plugins/openxr/src/OpenXrDisplayPlugin.h +++ b/plugins/openxr/src/OpenXrDisplayPlugin.h @@ -26,6 +26,8 @@ public: void init() override; + void idle() override; + float getTargetFrameRate() const override; bool hasAsyncReprojection() const override { return true; } @@ -84,4 +86,7 @@ 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 bdf98bd2c3..e0c36724a3 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -126,7 +126,7 @@ bool OpenXrInputPlugin::InputDevice::triggerHapticPulse(float strength, float du nanoseconds durationNs = duration_cast(milliseconds(static_cast(duration * 10.0f))); XrDuration xrDuration = durationNs.count(); - auto path = (index == 0) ? "hand_haptic_left" : "hand_haptic_right"; + auto path = (index == 0) ? "left_haptic" : "right_haptic"; if (!_actions.at(path)->applyHaptic(xrDuration, XR_FREQUENCY_UNSPECIFIED, 0.5f * strength)) { qCCritical(xr_input_cat) << "Failed to apply haptic feedback!"; @@ -312,32 +312,28 @@ controller::Input::NamedVector OpenXrInputPlugin::InputDevice::getAvailableInput QVector availableInputs{ makePair(HEAD, "Head"), + makePair(LEFT_HAND, "LeftHand"), - makePair(RIGHT_HAND, "RightHand"), - - // INPUT FIXME: Actions.Translate.{X,Z} work - // perfectly but Standard.LY is unreliable - makePair(LX, "WalkX"), - makePair(LY, "WalkY"), - - makePair(LT, "LeftInteract"), - makePair(RT, "RightInteract"), - makePair(LT_CLICK, "LeftInteractClick"), - makePair(RT_CLICK, "RightInteractClick"), - + makePair(LS, "LS"), + makePair(LS_TOUCH, "LSTouch"), + makePair(LX, "LX"), + makePair(LY, "LY"), + makePair(LT, "LT"), + makePair(LT_CLICK, "LTClick"), makePair(LEFT_GRIP, "LeftGrip"), - makePair(RIGHT_GRIP, "RightGrip"), + makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"), + makePair(LEFT_SECONDARY_THUMB, "LeftSecondaryThumb"), - // INPUT TODO: horrific hack that breaks depending on handedness - // because the input system is in dire need of a refactor, - // it was (mostly) designed with raw inputs in mind which makes - // it extremely difficult to map onto openxr actions - makePair(LEFT_PRIMARY_THUMB, "ToggleTablet"), - makePair(RIGHT_PRIMARY_THUMB, "Jump"), - makePair(DD, "Sprint"), - makePair(RX, "Turn"), - makePair(RY, "Teleport"), - makePair(LS_TOUCH, "CycleCamera"), + makePair(RIGHT_HAND, "RightHand"), + makePair(RS, "RS"), + makePair(RS_TOUCH, "RSTouch"), + makePair(RX, "RX"), + makePair(RY, "RY"), + makePair(RT, "RT"), + makePair(RT_CLICK, "RTClick"), + makePair(RIGHT_GRIP, "RightGrip"), + makePair(RIGHT_PRIMARY_THUMB, "RightPrimaryThumb"), + makePair(RIGHT_SECONDARY_THUMB, "RightSecondaryThumb"), }; return availableInputs; @@ -366,106 +362,109 @@ bool OpenXrInputPlugin::InputDevice::initActions() { return false; std::map> actionTypes = { - {"tablet", {"Toggle Tablet", XR_ACTION_TYPE_BOOLEAN_INPUT}}, - {"teleport", {"Teleport", XR_ACTION_TYPE_BOOLEAN_INPUT}}, - {"cycle_camera", {"Cycle Camera", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"left_primary_click", {"Left Primary", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"left_secondary_click", {"Left Secondary (Tablet)", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"left_squeeze_value", {"Left Squeeze", XR_ACTION_TYPE_FLOAT_INPUT}}, + {"left_trigger_value", {"Left Trigger", XR_ACTION_TYPE_FLOAT_INPUT}}, + {"left_trigger_click", {"Left Trigger Click", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"left_thumbstick", {"Left Thumbstick", XR_ACTION_TYPE_VECTOR2F_INPUT}}, + {"left_thumbstick_click", {"Left Thumbstick Click", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"left_thumbstick_touch", {"Left Thumbstick Touch", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"left_pose", {"Left Hand Pose", XR_ACTION_TYPE_POSE_INPUT}}, + {"left_haptic", {"Left Hand Haptic", XR_ACTION_TYPE_VIBRATION_OUTPUT}}, - {"interact_left", {"Left Interact", XR_ACTION_TYPE_FLOAT_INPUT}}, - {"interact_right", {"Right Interact", XR_ACTION_TYPE_FLOAT_INPUT}}, - - {"grip_left", {"Left Grip", XR_ACTION_TYPE_FLOAT_INPUT}}, - {"grip_right", {"Right Grip", XR_ACTION_TYPE_FLOAT_INPUT}}, - - {"turn_left", {"Turn Left", XR_ACTION_TYPE_BOOLEAN_INPUT}}, - {"turn_right", {"Turn Right", XR_ACTION_TYPE_BOOLEAN_INPUT}}, - {"walk", {"Walk", XR_ACTION_TYPE_VECTOR2F_INPUT}}, - {"sprint", {"Sprint", XR_ACTION_TYPE_BOOLEAN_INPUT}}, - {"jump", {"Jump", XR_ACTION_TYPE_BOOLEAN_INPUT}}, - - // in case the runtime doesn't support dpad emulation - {"stick_left", {"Left Stick (Fallback)", XR_ACTION_TYPE_VECTOR2F_INPUT}}, - {"stick_right", {"Right Stick (Fallback)", XR_ACTION_TYPE_VECTOR2F_INPUT}}, - {"stick_click_left", {"Left Stick Click (Fallback)", XR_ACTION_TYPE_BOOLEAN_INPUT}}, - {"stick_click_right", {"Right Stick Click (Fallback)", XR_ACTION_TYPE_BOOLEAN_INPUT}}, - - {"hand_pose_left", {"Left Hand Pose", XR_ACTION_TYPE_POSE_INPUT}}, - {"hand_pose_right", {"Right Hand Pose", XR_ACTION_TYPE_POSE_INPUT}}, - {"hand_haptic_left", {"Left Hand Haptic", XR_ACTION_TYPE_VIBRATION_OUTPUT}}, - {"hand_haptic_right", {"Right Hand Haptic", XR_ACTION_TYPE_VIBRATION_OUTPUT}}, + {"right_primary_click", {"Right Primary", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"right_secondary_click", {"Right Secondary (Jump)", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"right_squeeze_value", {"Right Squeeze", XR_ACTION_TYPE_FLOAT_INPUT}}, + {"right_trigger_value", {"Right Trigger", XR_ACTION_TYPE_FLOAT_INPUT}}, + {"right_trigger_click", {"Right Trigger Click", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"right_thumbstick", {"Right Thumbstick", XR_ACTION_TYPE_VECTOR2F_INPUT}}, + {"right_thumbstick_click", {"Right Thumbstick Click", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"right_thumbstick_touch", {"Right Thumbstick Touch", XR_ACTION_TYPE_BOOLEAN_INPUT}}, + {"right_pose", {"Right Hand Pose", XR_ACTION_TYPE_POSE_INPUT}}, + {"right_haptic", {"Right Hand Haptic", XR_ACTION_TYPE_VIBRATION_OUTPUT}}, }; - // palm pose is nice but monado doesn't support it yet - // (disable it for now because i can't test what palm - // pose looks like and if it'll break something) - //auto hand_pose_name = (_context->_palmPoseSupported) ? "/palm_ext/pose" : "/grip/pose"; - auto hand_pose_name = "/grip/pose"; + std::string hand_left = "/user/hand/left/input"; + std::string hand_right = "/user/hand/right/input"; - // TODO: set up the openxr dpad bindings modifier (looks complicated) std::map> actionSuggestions = { + // not really usable, bare minimum {"/interaction_profiles/khr/simple_controller", { - {"tablet", "/user/hand/left/input/menu/click"}, - {"teleport", "/user/hand/right/input/menu/click"}, - {"interact_left", "/user/hand/left/input/select/click"}, - {"interact_right", "/user/hand/right/input/select/click"}, - {"hand_pose_left", std::string("/user/hand/left/input") + hand_pose_name}, - {"hand_pose_right", std::string("/user/hand/right/input") + hand_pose_name}, - {"hand_haptic_left", "/user/hand/left/output/haptic"}, - {"hand_haptic_right", "/user/hand/right/output/haptic"}, + {"left_secondary_click", hand_left + "/menu/click"}, + {"left_trigger_click", hand_left + "/select/click"}, + {"left_pose", hand_left + "/grip/pose"}, + {"left_haptic", "/user/hand/left/output/haptic"}, + + {"right_secondary_click", hand_right + "/menu/click"}, + {"right_trigger_click", hand_right + "/select/click"}, + {"right_pose", hand_right + "/grip/pose"}, + {"right_haptic", "/user/hand/right/output/haptic"}, }}, {"/interaction_profiles/htc/vive_controller", { - {"tablet", "/user/hand/left/input/menu/click"}, - //{"teleport", "/user/hand/right/input/trackpad/dpad_up"}, - //{"cycle_camera", "/user/hand/right/input/trackpad/dpad_down"}, - {"interact_left", "/user/hand/left/input/trigger/value"}, - {"interact_right", "/user/hand/right/input/trigger/value"}, - {"grip_left", "/user/hand/left/input/squeeze/click"}, - {"grip_right", "/user/hand/right/input/squeeze/click"}, - {"jump", "/user/hand/right/input/menu/click"}, - {"walk", "/user/hand/left/input/trackpad"}, - //{"turn_left", "/user/hand/right/input/trackpad/dpad_left"}, - //{"turn_right", "/user/hand/right/input/trackpad/dpad_right"}, - {"stick_left", "/user/hand/left/input/trackpad"}, - {"stick_right", "/user/hand/right/input/trackpad"}, - {"stick_click_left", "/user/hand/left/input/trackpad/click"}, - {"stick_click_right", "/user/hand/right/input/trackpad/click"}, - {"hand_pose_left", std::string("/user/hand/left/input") + hand_pose_name}, - {"hand_pose_right", std::string("/user/hand/right/input") + hand_pose_name}, - {"hand_haptic_left", "/user/hand/left/output/haptic"}, - {"hand_haptic_right", "/user/hand/right/output/haptic"}, + {"left_secondary_click", hand_left + "/menu/click"}, + {"left_squeeze_value", hand_left + "/squeeze/click"}, + {"left_trigger_value", hand_left + "/trigger/value"}, + {"left_trigger_click", hand_left + "/trigger/click"}, + {"left_thumbstick", hand_left + "/trackpad"}, + {"left_thumbstick_click", hand_left + "/trackpad/click"}, + {"left_thumbstick_touch", hand_left + "/trackpad/touch"}, + {"left_pose", hand_left + "/grip/pose"}, + {"left_haptic", "/user/hand/left/output/haptic"}, + + {"right_secondary_click", hand_right + "/menu/click"}, + {"right_squeeze_value", hand_right + "/squeeze/click"}, + {"right_trigger_value", hand_right + "/trigger/value"}, + {"right_trigger_click", hand_right + "/trigger/click"}, + {"right_thumbstick", hand_right + "/trackpad"}, + {"right_thumbstick_click", hand_right + "/trackpad/click"}, + {"right_thumbstick_touch", hand_right + "/trackpad/touch"}, + {"right_pose", hand_right + "/grip/pose"}, + {"right_haptic", "/user/hand/right/output/haptic"}, }}, - {"/interaction_profiles/oculus/touch_controller", { - {"tablet", "/user/hand/left/input/y/click"}, - {"interact_left", "/user/hand/left/input/trigger/value"}, - {"interact_right", "/user/hand/right/input/trigger/value"}, - {"grip_left", "/user/hand/left/input/squeeze/value"}, - {"grip_right", "/user/hand/right/input/squeeze/value"}, - {"jump", "/user/hand/right/input/b/click"}, - {"walk", "/user/hand/left/input/thumbstick"}, - {"stick_left", "/user/hand/left/input/thumbstick"}, - {"stick_right", "/user/hand/right/input/thumbstick"}, - {"stick_click_left", "/user/hand/left/input/thumbstick/click"}, - {"stick_click_right", "/user/hand/right/input/thumbstick/click"}, - {"hand_pose_left", std::string("/user/hand/left/input") + hand_pose_name}, - {"hand_pose_right", std::string("/user/hand/right/input") + hand_pose_name}, - {"hand_haptic_left", "/user/hand/left/output/haptic"}, - {"hand_haptic_right", "/user/hand/right/output/haptic"}, + {"/interaction_profiles/oculus_touch_controller", { + {"left_primary_click", hand_left + "/x/click"}, + {"left_secondary_click", hand_left + "/y/click"}, + {"left_squeeze_value", hand_left + "/squeeze/value"}, + {"left_trigger_value", hand_left + "/trigger/value"}, + {"left_trigger_click", hand_left + "/trigger/click"}, + {"left_thumbstick", hand_left + "/thumbstick"}, + {"left_thumbstick_click", hand_left + "/thumbstick/click"}, + {"left_thumbstick_touch", hand_left + "/thumbstick/touch"}, + {"left_pose", hand_left + "/grip/pose"}, + {"left_haptic", "/user/hand/left/output/haptic"}, + + {"right_primary_click", hand_right + "/a/click"}, + {"right_secondary_click", hand_right + "/b/click"}, + {"right_squeeze_value", hand_right + "/squeeze/value"}, + {"right_trigger_value", hand_right + "/trigger/value"}, + {"right_trigger_click", hand_right + "/trigger/click"}, + {"right_thumbstick", hand_right + "/thumbstick"}, + {"right_thumbstick_click", hand_right + "/thumbstick/click"}, + {"right_thumbstick_touch", hand_right + "/thumbstick/touch"}, + {"right_pose", hand_right + "/grip/pose"}, + {"right_haptic", "/user/hand/right/output/haptic"}, }}, {"/interaction_profiles/microsoft/motion_controller", { - {"tablet", "/user/hand/left/input/menu/click"}, - {"interact_left", "/user/hand/left/input/trigger/value"}, - {"interact_right", "/user/hand/right/input/trigger/value"}, - {"grip_left", "/user/hand/left/input/squeeze/click"}, - {"grip_right", "/user/hand/right/input/squeeze/click"}, - {"jump", "/user/hand/right/input/menu/click"}, - {"walk", "/user/hand/left/input/thumbstick"}, - {"stick_left", "/user/hand/left/input/thumbstick"}, - {"stick_right", "/user/hand/right/input/thumbstick"}, - {"stick_click_left", "/user/hand/left/input/thumbstick/click"}, - {"stick_click_right", "/user/hand/right/input/thumbstick/click"}, - {"hand_pose_left", std::string("/user/hand/left/input") + hand_pose_name}, - {"hand_pose_right", std::string("/user/hand/right/input") + hand_pose_name}, - {"hand_haptic_left", "/user/hand/left/output/haptic"}, - {"hand_haptic_right", "/user/hand/right/output/haptic"}, + {"left_secondary_click", hand_left + "/menu/click"}, + {"left_squeeze_value", hand_left + "/squeeze/click"}, + {"left_trigger_value", hand_left + "/trigger/value"}, + {"left_trigger_click", hand_left + "/trigger/click"}, + {"left_thumbstick", hand_left + "/thumbstick"}, + {"left_thumbstick_click", hand_left + "/trackpad/click"}, + {"left_thumbstick_touch", hand_left + "/trackpad/touch"}, + {"left_pose", hand_left + "/grip/pose"}, + {"left_haptic", "/user/hand/left/output/haptic"}, + + {"right_secondary_click", hand_right + "/menu/click"}, + {"right_squeeze_value", hand_right + "/squeeze/click"}, + {"right_trigger_value", hand_right + "/trigger/value"}, + {"right_trigger_click", hand_right + "/trigger/click"}, + {"right_thumbstick", hand_right + "/thumbstick"}, + {"right_thumbstick_click", hand_right + "/trackpad/click"}, + {"right_thumbstick_touch", hand_right + "/trackpad/touch"}, + {"right_pose", hand_right + "/grip/pose"}, + {"right_haptic", "/user/hand/right/output/haptic"}, }}, }; @@ -544,7 +543,7 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand; for (int i = 0; i < HAND_COUNT; i++) { - auto hand_path = (i == 0) ? "hand_pose_left" : "hand_pose_right"; + auto hand_path = (i == 0) ? "left_pose" : "right_pose"; XrSpaceLocation handLocation = _actions.at(hand_path)->getPose(); bool locationValid = (handLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0; if (locationValid) { @@ -553,8 +552,6 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I auto pose = controller::Pose(translation, rotation); glm::mat4 handOffset = i == 0 ? glm::toMat4(leftRotationOffset) : glm::toMat4(rightRotationOffset); - // offset constants taken from OpenComposite - // and tweaked to fit my hands as best i could glm::mat4 posOffset(1.0f); posOffset *= glm::translate(glm::vec3(handOffset[0]) * (i == 0 ? 0.1f : -0.1f)); posOffset *= glm::translate(glm::vec3(handOffset[1]) * -0.16f); @@ -570,7 +567,7 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I // align the eyes of the user with the eyes of the avatar defaultHeadOffset = Matrices::Y_180 * (glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat); - // dont double up on eye offset + // don't double up on eye offset eyeZOffset = 0.0f; } else { defaultHeadOffset = createMatFromQuatAndPos(-DEFAULT_AVATAR_HEAD_ROT, -DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET); @@ -582,10 +579,11 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I _poseStateMap[controller::HEAD] = _context->_lastHeadPose.postTransform(headCorrectionA).postTransform(defaultHeadOffset).postTransform(headCorrectionB).transform(sensorToAvatar); std::vector> floatsToUpdate = { - {"interact_left", controller::LT}, - {"interact_right", controller::RT}, - {"grip_left", controller::LEFT_GRIP}, - {"grip_right", controller::RIGHT_GRIP}, + {"left_trigger_value", controller::LT}, + {"left_squeeze_value", controller::LEFT_GRIP}, + + {"right_trigger_value", controller::RT}, + {"right_squeeze_value", controller::RIGHT_GRIP}, }; for (const auto& [name, channel] : floatsToUpdate) { @@ -596,9 +594,8 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I } std::vector> axesToUpdate = { - //{"stick_left", controller::LX, controller::LY}, - //{"stick_right", controller::RX, controller::RY}, - {"walk", controller::LX, controller::LY}, + {"left_thumbstick", controller::LX, controller::LY}, + {"right_thumbstick", controller::RX, controller::RY}, }; for (const auto& [name, x_channel, y_channel] : axesToUpdate) { @@ -609,36 +606,18 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I } } - // INPUT TODO: more hacks - { - auto turn_left = _actions.at("turn_left")->getBool(); - if (turn_left.isActive && turn_left.currentState) { - _axisStateMap[controller::RX].value -= 1.0f; - } - - auto turn_right = _actions.at("turn_right")->getBool(); - if (turn_right.isActive && turn_right.currentState) { - _axisStateMap[controller::RX].value += 1.0f; - } - - // INPUT TODO: the teleport script is hardcoded to use the LY/RY axes - auto teleport = _actions.at("teleport")->getBool(); - if (teleport.isActive && teleport.currentState) { - _axisStateMap[controller::RY].value += 1.0f; - } - } - - // don't double up on the stick values between the proper actions and fallback sticks - _axisStateMap[controller::LX].value = std::clamp(_axisStateMap[controller::LX].value, -1.0f, 1.0f); - _axisStateMap[controller::LY].value = std::clamp(_axisStateMap[controller::LY].value, -1.0f, 1.0f); - _axisStateMap[controller::RX].value = std::clamp(_axisStateMap[controller::RX].value, -1.0f, 1.0f); - _axisStateMap[controller::RY].value = std::clamp(_axisStateMap[controller::RY].value, -1.0f, 1.0f); - std::vector> buttonsToUpdate = { - {"tablet", controller::LEFT_PRIMARY_THUMB}, - {"jump", controller::RIGHT_PRIMARY_THUMB}, - {"cycle_camera", controller::LS_TOUCH}, - {"sprint", controller::DD}, + {"left_primary_click", controller::LEFT_PRIMARY_THUMB}, + {"left_secondary_click", controller::LEFT_SECONDARY_THUMB}, + {"left_trigger_click", controller::LT_CLICK}, + {"left_thumbstick_click", controller::LS}, + {"left_thumbstick_touch", controller::LS_TOUCH}, + + {"right_primary_click", controller::RIGHT_PRIMARY_THUMB}, + {"right_secondary_click", controller::RIGHT_SECONDARY_THUMB}, + {"right_trigger_click", controller::RT_CLICK}, + {"right_thumbstick_click", controller::RS}, + {"right_thumbstick_touch", controller::RS_TOUCH}, }; for (const auto& [name, channel] : buttonsToUpdate) { @@ -648,93 +627,73 @@ void OpenXrInputPlugin::InputDevice::update(float deltaTime, const controller::I } } - // INPUT TODO: it's really not necessary to expose "interact click" bindings, - // but the engine expects there to be click buttons to work properly - { - auto left = _actions.at("interact_left")->getFloat(); - if (left.isActive && left.currentState == 1.0f) { - _buttonPressedMap.insert(controller::LT_CLICK); - } + awfulRightStickHackForBrokenScripts(); - auto right = _actions.at("interact_right")->getFloat(); - if (right.isActive && right.currentState == 1.0f) { - _buttonPressedMap.insert(controller::RT_CLICK); - } + if (_context->_stickEmulation) { + emulateStickFromTrackpad(); } - - // emulate dpad if the dpad extension isn't available - emulateStickDPad(); } -void OpenXrInputPlugin::InputDevice::emulateStickDPad() { - auto right_stick = _actions.at("stick_right")->getVector2f(); +void OpenXrInputPlugin::InputDevice::emulateStickFromTrackpad() { + auto left_stick = _actions.at("left_thumbstick")->getVector2f().currentState; + auto right_stick = _actions.at("right_thumbstick")->getVector2f().currentState; + auto left_click = _actions.at("left_thumbstick_click")->getBool().currentState; + auto right_click = _actions.at("right_thumbstick_click")->getBool().currentState; - auto left_stick_click = _actions.at("stick_click_left")->getBool(); - auto right_stick_click = _actions.at("stick_click_right")->getBool(); - - auto left_needs_click = _context->_dpadNeedsClick && left_stick_click.isActive; - auto right_needs_click = _context->_dpadNeedsClick && left_stick_click.isActive; - - auto left_clicked = left_needs_click ? left_stick_click.currentState : true; - auto right_clicked = right_needs_click ? right_stick_click.currentState : true; - - if (right_stick.isActive && right_clicked) { - // camera switch (right stick down) - if ( - right_stick.currentState.y < -0.6f - && right_stick.currentState.x > -0.4f - && right_stick.currentState.x < 0.4f - ) { - _buttonPressedMap.insert(controller::LS_TOUCH); - } - - // snap turn left - if ( - right_stick.currentState.x < -0.6f - && right_stick.currentState.y > -0.4f - && right_stick.currentState.y < 0.4f - ) { - _axisStateMap[controller::RX].value = -1.0f; - _axisStateMap[controller::RY].value = 0.0f; - } - - // snap turn right - if ( - right_stick.currentState.x > 0.6f - && right_stick.currentState.y > -0.4f - && right_stick.currentState.y < 0.4f - ) { - _axisStateMap[controller::RX].value = 1.0f; - _axisStateMap[controller::RY].value = 0.0f; - } - - // teleport - if ( - right_stick.currentState.y > 0.6f - && right_stick.currentState.x > -0.4f - && right_stick.currentState.x < 0.4f - ) { - _axisStateMap[controller::RX].value = 0.0f; - _axisStateMap[controller::RY].value = -1.0f; - } - } - - // set stick inputs to zero if they're not - // clicked in or too close to the center - if ( - !right_clicked - || (right_stick.currentState.x > -0.6f - && right_stick.currentState.x < 0.6f - && right_stick.currentState.y > -0.6f - && right_stick.currentState.y < 0.6f) - ) { + // set the axes to zero if the trackpad isn't clicked in + if (!right_click) { _axisStateMap[controller::RX].value = 0.0f; _axisStateMap[controller::RY].value = 0.0f; } - // don't walk unless the trackpad is clicked in - if (!left_clicked) { + if (!left_click) { _axisStateMap[controller::LX].value = 0.0f; _axisStateMap[controller::LY].value = 0.0f; } + + // "primary" button on trackpad center + if ( + left_click && + left_stick.x > -0.3f && + left_stick.x < 0.3f && + left_stick.y > -0.3f && + left_stick.y < 0.3f + ) { + _buttonPressedMap.insert(controller::LEFT_PRIMARY_THUMB); + } + + if ( + right_click && + right_stick.x > -0.3f && + right_stick.x < 0.3f && + right_stick.y > -0.3f && + right_stick.y < 0.3f + ) { + _buttonPressedMap.insert(controller::RIGHT_PRIMARY_THUMB); + } +} + +// FIXME: the vr controller scripts are horribly broken and don't work properly, +// this emulates a segmented vive trackpad to get teleport and snap turning behaving +void OpenXrInputPlugin::InputDevice::awfulRightStickHackForBrokenScripts() { + auto stick = _actions.at("right_thumbstick")->getVector2f().currentState; + + _axisStateMap[controller::RX].value = 0.0f; + _axisStateMap[controller::RY].value = 0.0f; + + if (stick.x < -0.6f && stick.y > -0.4f && stick.y < 0.4f) { + _axisStateMap[controller::RX].value = -1.0f; + } + + if (stick.x > 0.6f && stick.y > -0.4f && stick.y < 0.4f) { + _axisStateMap[controller::RX].value = 1.0f; + } + + if (stick.y > 0.6f && stick.x > -0.4f && stick.x < 0.4f) { + _axisStateMap[controller::RY].value = -1.0f; + } + + if (stick.y < -0.6f && stick.x > -0.4f && stick.x < 0.4f) { + _axisStateMap[controller::RY].value = 1.0f; + } } diff --git a/plugins/openxr/src/OpenXrInputPlugin.h b/plugins/openxr/src/OpenXrInputPlugin.h index 63614dbcf9..7e3549bfcb 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.h +++ b/plugins/openxr/src/OpenXrInputPlugin.h @@ -82,7 +82,8 @@ private: void focusOutEvent() override; bool triggerHapticPulse(float strength, float duration, uint16_t index) override; - void emulateStickDPad(); + void emulateStickFromTrackpad(); + void awfulRightStickHackForBrokenScripts(); mutable std::recursive_mutex _lock; template From d84b306e21bf04b13d6d791880c838ef15f2f047 Mon Sep 17 00:00:00 2001 From: Ada Date: Thu, 3 Apr 2025 03:12:44 +1000 Subject: [PATCH 40/43] Add launch flag to mutually exclusively enable XR/OpenVR --- interface/src/Application_Plugins.cpp | 12 ++++++++++++ interface/src/main.cpp | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/interface/src/Application_Plugins.cpp b/interface/src/Application_Plugins.cpp index 77786da424..6c41ae2e59 100644 --- a/interface/src/Application_Plugins.cpp +++ b/interface/src/Application_Plugins.cpp @@ -73,6 +73,18 @@ void Application::initializePluginManager(const QCommandLineParser& parser) { qInfo() << "Disabling following input plugins:" << disabledInputs; PluginManager::getInstance()->disableInputs(disabledInputs); } + + if (parser.isSet("useExperimentalXR")) { + auto pluginNames = QStringList(); + pluginNames.push_back("OpenVR (Vive)"); + PluginManager::getInstance()->disableDisplays(pluginNames); + PluginManager::getInstance()->disableInputs(pluginNames); + } else { + auto pluginNames = QStringList(); + pluginNames.push_back("OpenXR"); + PluginManager::getInstance()->disableDisplays(pluginNames); + PluginManager::getInstance()->disableInputs(pluginNames); + } } void Application::shutdownPlugins() {} diff --git a/interface/src/main.cpp b/interface/src/main.cpp index a3141ccb95..d67a1d0c82 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -286,6 +286,10 @@ int main(int argc, const char* argv[]) { "getProtocolVersionData", "Debug option. Returns the network protocol detailed data in JSON." ); + QCommandLineOption useExperimentalXR( + "useExperimentalXR", + "Enables the experimental OpenXR plugin and disables the OpenVR plugin. Some features available in OpenVR aren't yet available in OpenXR." + ); // "--qmljsdebugger", which appears in output from "--help-all". // Those below don't seem to be optional. @@ -335,6 +339,7 @@ int main(int argc, const char* argv[]) { parser.addOption(getPluginsOption); parser.addOption(getProtocolVersionHashOption); parser.addOption(getProtocolVersionDataOption); + parser.addOption(useExperimentalXR); QString applicationPath; From bfcd52b5dc0e9b0ad725dba50a84c8a4f9bae62c Mon Sep 17 00:00:00 2001 From: Ada Date: Sat, 5 Apr 2025 17:20:34 +1000 Subject: [PATCH 41/43] TODO comments for unimplemented input config functions --- plugins/openxr/src/OpenXrInputPlugin.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index e0c36724a3..bca1de68a7 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -24,12 +24,14 @@ OpenXrInputPlugin::OpenXrInputPlugin(std::shared_ptr c) { _inputDevice = std::make_shared(_context); } -// TODO: Make a config UI +// TODO: Config options static const QString XR_CONFIGURATION_LAYOUT = QString(""); +// TODO: full-body-tracking void OpenXrInputPlugin::calibrate() { } +// TODO: full-body-tracking bool OpenXrInputPlugin::uncalibrate() { return true; } @@ -38,9 +40,11 @@ bool OpenXrInputPlugin::isSupported() const { return _context->_isSupported; } +// TODO: Config options void OpenXrInputPlugin::setConfigurationSettings(const QJsonObject configurationSettings) { } +// TODO: Config options QJsonObject OpenXrInputPlugin::configurationSettings() { return QJsonObject(); } @@ -96,9 +100,11 @@ void OpenXrInputPlugin::pluginUpdate(float deltaTime, const controller::InputCal } } +// TODO: Config options void OpenXrInputPlugin::loadSettings() { } +// TODO: Config options void OpenXrInputPlugin::saveSettings() const { } From 8d3a221970ac7b57b49e1259832de89f5e54d331 Mon Sep 17 00:00:00 2001 From: Ada Date: Sat, 5 Apr 2025 17:27:30 +1000 Subject: [PATCH 42/43] Fix oculus controller path, add index controller bindings --- plugins/openxr/src/OpenXrInputPlugin.cpp | 25 +++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/plugins/openxr/src/OpenXrInputPlugin.cpp b/plugins/openxr/src/OpenXrInputPlugin.cpp index bca1de68a7..d18fa96627 100644 --- a/plugins/openxr/src/OpenXrInputPlugin.cpp +++ b/plugins/openxr/src/OpenXrInputPlugin.cpp @@ -428,7 +428,7 @@ bool OpenXrInputPlugin::InputDevice::initActions() { {"right_pose", hand_right + "/grip/pose"}, {"right_haptic", "/user/hand/right/output/haptic"}, }}, - {"/interaction_profiles/oculus_touch_controller", { + {"/interaction_profiles/oculus/touch_controller", { {"left_primary_click", hand_left + "/x/click"}, {"left_secondary_click", hand_left + "/y/click"}, {"left_squeeze_value", hand_left + "/squeeze/value"}, @@ -472,6 +472,29 @@ bool OpenXrInputPlugin::InputDevice::initActions() { {"right_pose", hand_right + "/grip/pose"}, {"right_haptic", "/user/hand/right/output/haptic"}, }}, + {"/interaction_profiles/valve/index_controller", { + {"left_primary_click", hand_left + "/a/click"}, + {"left_secondary_click", hand_left + "/b/click"}, + {"left_squeeze_value", hand_left + "/squeeze/value"}, + {"left_trigger_value", hand_left + "/trigger/value"}, + {"left_trigger_click", hand_left + "/trigger/click"}, + {"left_thumbstick", hand_left + "/thumbstick"}, + {"left_thumbstick_click", hand_left + "/thumbstick/click"}, + {"left_thumbstick_touch", hand_left + "/thumbstick/touch"}, + {"left_pose", hand_left + "/grip/pose"}, + {"left_haptic", "/user/hand/left/output/haptic"}, + + {"right_primary_click", hand_right + "/a/click"}, + {"right_secondary_click", hand_right + "/b/click"}, + {"right_squeeze_value", hand_right + "/squeeze/value"}, + {"right_trigger_value", hand_right + "/trigger/value"}, + {"right_trigger_click", hand_right + "/trigger/click"}, + {"right_thumbstick", hand_right + "/thumbstick"}, + {"right_thumbstick_click", hand_right + "/thumbstick/click"}, + {"right_thumbstick_touch", hand_right + "/thumbstick/touch"}, + {"right_pose", hand_right + "/grip/pose"}, + {"right_haptic", "/user/hand/right/output/haptic"}, + }}, }; for (const auto& [id, args] : actionTypes) { From d4d46cdbe8d400c18512074fd7a14e13e914e2f8 Mon Sep 17 00:00:00 2001 From: Ada Date: Sat, 5 Apr 2025 23:04:59 +1000 Subject: [PATCH 43/43] Use OpenXR 1.0, some runtimes don't support 1.1 yet --- plugins/openxr/src/OpenXrContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/openxr/src/OpenXrContext.cpp b/plugins/openxr/src/OpenXrContext.cpp index 269486b313..d999671959 100644 --- a/plugins/openxr/src/OpenXrContext.cpp +++ b/plugins/openxr/src/OpenXrContext.cpp @@ -124,7 +124,7 @@ bool OpenXrContext::initInstance() { .applicationVersion = 1, .engineName = "Overte", .engineVersion = 0, - .apiVersion = XR_CURRENT_API_VERSION, + .apiVersion = XR_API_VERSION_1_0, }, .enabledExtensionCount = (uint32_t)enabled.size(), .enabledExtensionNames = enabled.data(),