From 0431e7cb3cae0dce679059ba20c6406c88654915 Mon Sep 17 00:00:00 2001 From: James Hogan <james@albanarts.com> Date: Thu, 13 Jan 2022 16:23:51 +0000 Subject: [PATCH] 3rdparty: Import osgXR 0.3.7+ Import osgXR from https://github.com/amalon/osgXR master branch into 3rdparty, specifically commit b7e222775553b529018ac4b847353327c24ae5d4, which is 0.3.7 with tweaks for building as a subproject in a subdirectory. This will allow VR support to be more conveniently built if not already installed, without having to fetch yet another dependency. --- 3rdparty/osgXR/CHANGELOG.md | 184 ++ 3rdparty/osgXR/CMakeLists.txt | 79 + 3rdparty/osgXR/Config.cmake.in | 14 + 3rdparty/osgXR/LICENSE.txt | 502 ++++++ 3rdparty/osgXR/README.md | 73 + 3rdparty/osgXR/docs/API.md | 102 ++ 3rdparty/osgXR/examples/CMakeLists.txt | 29 + 3rdparty/osgXR/examples/osgteapot.cpp | 365 ++++ 3rdparty/osgXR/include/osgXR/Action | 471 +++++ 3rdparty/osgXR/include/osgXR/ActionSet | 138 ++ 3rdparty/osgXR/include/osgXR/Export | 22 + .../osgXR/include/osgXR/InteractionProfile | 74 + 3rdparty/osgXR/include/osgXR/Manager | 237 +++ 3rdparty/osgXR/include/osgXR/Mirror | 50 + 3rdparty/osgXR/include/osgXR/MirrorSettings | 72 + 3rdparty/osgXR/include/osgXR/OpenXRDisplay | 49 + 3rdparty/osgXR/include/osgXR/Settings | 345 ++++ 3rdparty/osgXR/include/osgXR/Subaction | 75 + 3rdparty/osgXR/include/osgXR/View | 79 + 3rdparty/osgXR/include/osgXR/osgXR | 22 + 3rdparty/osgXR/osgXR.pc.in | 12 + 3rdparty/osgXR/src/Action.cpp | 507 ++++++ 3rdparty/osgXR/src/Action.h | 84 + 3rdparty/osgXR/src/ActionSet.cpp | 242 +++ 3rdparty/osgXR/src/ActionSet.h | 93 + 3rdparty/osgXR/src/CMakeLists.txt | 132 ++ 3rdparty/osgXR/src/Config.in | 10 + 3rdparty/osgXR/src/FrameStampedVector.h | 86 + 3rdparty/osgXR/src/FrameStore.cpp | 87 + 3rdparty/osgXR/src/FrameStore.h | 63 + 3rdparty/osgXR/src/InteractionProfile.cpp | 106 ++ 3rdparty/osgXR/src/InteractionProfile.h | 93 + 3rdparty/osgXR/src/Manager.cpp | 191 ++ 3rdparty/osgXR/src/Mirror.cpp | 139 ++ 3rdparty/osgXR/src/MirrorSettings.cpp | 12 + 3rdparty/osgXR/src/OpenXR/Action.cpp | 188 ++ 3rdparty/osgXR/src/OpenXR/Action.h | 371 ++++ 3rdparty/osgXR/src/OpenXR/ActionSet.cpp | 35 + 3rdparty/osgXR/src/OpenXR/ActionSet.h | 69 + 3rdparty/osgXR/src/OpenXR/Compositor.cpp | 71 + 3rdparty/osgXR/src/OpenXR/Compositor.h | 92 + 3rdparty/osgXR/src/OpenXR/DepthInfo.h | 80 + 3rdparty/osgXR/src/OpenXR/EventHandler.cpp | 182 ++ 3rdparty/osgXR/src/OpenXR/EventHandler.h | 75 + 3rdparty/osgXR/src/OpenXR/GraphicsBinding.cpp | 69 + 3rdparty/osgXR/src/OpenXR/GraphicsBinding.h | 48 + .../osgXR/src/OpenXR/GraphicsBindingWin32.cpp | 14 + .../osgXR/src/OpenXR/GraphicsBindingWin32.h | 29 + .../osgXR/src/OpenXR/GraphicsBindingX11.cpp | 40 + .../osgXR/src/OpenXR/GraphicsBindingX11.h | 29 + 3rdparty/osgXR/src/OpenXR/Instance.cpp | 398 +++++ 3rdparty/osgXR/src/OpenXR/Instance.h | 179 ++ .../osgXR/src/OpenXR/InteractionProfile.cpp | 59 + .../osgXR/src/OpenXR/InteractionProfile.h | 77 + 3rdparty/osgXR/src/OpenXR/Path.cpp | 41 + 3rdparty/osgXR/src/OpenXR/Path.h | 80 + 3rdparty/osgXR/src/OpenXR/Session.cpp | 519 ++++++ 3rdparty/osgXR/src/OpenXR/Session.h | 376 ++++ 3rdparty/osgXR/src/OpenXR/Space.cpp | 94 + 3rdparty/osgXR/src/OpenXR/Space.h | 126 ++ 3rdparty/osgXR/src/OpenXR/Swapchain.cpp | 170 ++ 3rdparty/osgXR/src/OpenXR/Swapchain.h | 119 ++ 3rdparty/osgXR/src/OpenXR/SwapchainGroup.cpp | 55 + 3rdparty/osgXR/src/OpenXR/SwapchainGroup.h | 115 ++ .../osgXR/src/OpenXR/SwapchainGroupSubImage.h | 114 ++ 3rdparty/osgXR/src/OpenXR/System.cpp | 122 ++ 3rdparty/osgXR/src/OpenXR/System.h | 221 +++ 3rdparty/osgXR/src/OpenXRDisplay.cpp | 42 + 3rdparty/osgXR/src/Settings.cpp | 63 + 3rdparty/osgXR/src/Subaction.cpp | 109 ++ 3rdparty/osgXR/src/Subaction.h | 75 + 3rdparty/osgXR/src/Version.h.in | 12 + 3rdparty/osgXR/src/View.cpp | 16 + 3rdparty/osgXR/src/XRFramebuffer.cpp | 151 ++ 3rdparty/osgXR/src/XRFramebuffer.h | 52 + 3rdparty/osgXR/src/XRRealizeOperation.cpp | 35 + 3rdparty/osgXR/src/XRRealizeOperation.h | 38 + 3rdparty/osgXR/src/XRState.cpp | 1589 +++++++++++++++++ 3rdparty/osgXR/src/XRState.h | 590 ++++++ 3rdparty/osgXR/src/XRStateCallbacks.h | 195 ++ 3rdparty/osgXR/src/osgXR.cpp | 94 + 3rdparty/osgXR/src/projection.cpp | 79 + 3rdparty/osgXR/src/projection.h | 20 + 83 files changed, 12256 insertions(+) create mode 100644 3rdparty/osgXR/CHANGELOG.md create mode 100644 3rdparty/osgXR/CMakeLists.txt create mode 100644 3rdparty/osgXR/Config.cmake.in create mode 100644 3rdparty/osgXR/LICENSE.txt create mode 100644 3rdparty/osgXR/README.md create mode 100644 3rdparty/osgXR/docs/API.md create mode 100644 3rdparty/osgXR/examples/CMakeLists.txt create mode 100644 3rdparty/osgXR/examples/osgteapot.cpp create mode 100644 3rdparty/osgXR/include/osgXR/Action create mode 100644 3rdparty/osgXR/include/osgXR/ActionSet create mode 100644 3rdparty/osgXR/include/osgXR/Export create mode 100644 3rdparty/osgXR/include/osgXR/InteractionProfile create mode 100644 3rdparty/osgXR/include/osgXR/Manager create mode 100644 3rdparty/osgXR/include/osgXR/Mirror create mode 100644 3rdparty/osgXR/include/osgXR/MirrorSettings create mode 100644 3rdparty/osgXR/include/osgXR/OpenXRDisplay create mode 100644 3rdparty/osgXR/include/osgXR/Settings create mode 100644 3rdparty/osgXR/include/osgXR/Subaction create mode 100644 3rdparty/osgXR/include/osgXR/View create mode 100644 3rdparty/osgXR/include/osgXR/osgXR create mode 100644 3rdparty/osgXR/osgXR.pc.in create mode 100644 3rdparty/osgXR/src/Action.cpp create mode 100644 3rdparty/osgXR/src/Action.h create mode 100644 3rdparty/osgXR/src/ActionSet.cpp create mode 100644 3rdparty/osgXR/src/ActionSet.h create mode 100644 3rdparty/osgXR/src/CMakeLists.txt create mode 100644 3rdparty/osgXR/src/Config.in create mode 100644 3rdparty/osgXR/src/FrameStampedVector.h create mode 100644 3rdparty/osgXR/src/FrameStore.cpp create mode 100644 3rdparty/osgXR/src/FrameStore.h create mode 100644 3rdparty/osgXR/src/InteractionProfile.cpp create mode 100644 3rdparty/osgXR/src/InteractionProfile.h create mode 100644 3rdparty/osgXR/src/Manager.cpp create mode 100644 3rdparty/osgXR/src/Mirror.cpp create mode 100644 3rdparty/osgXR/src/MirrorSettings.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/Action.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/Action.h create mode 100644 3rdparty/osgXR/src/OpenXR/ActionSet.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/ActionSet.h create mode 100644 3rdparty/osgXR/src/OpenXR/Compositor.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/Compositor.h create mode 100644 3rdparty/osgXR/src/OpenXR/DepthInfo.h create mode 100644 3rdparty/osgXR/src/OpenXR/EventHandler.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/EventHandler.h create mode 100644 3rdparty/osgXR/src/OpenXR/GraphicsBinding.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/GraphicsBinding.h create mode 100644 3rdparty/osgXR/src/OpenXR/GraphicsBindingWin32.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/GraphicsBindingWin32.h create mode 100644 3rdparty/osgXR/src/OpenXR/GraphicsBindingX11.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/GraphicsBindingX11.h create mode 100644 3rdparty/osgXR/src/OpenXR/Instance.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/Instance.h create mode 100644 3rdparty/osgXR/src/OpenXR/InteractionProfile.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/InteractionProfile.h create mode 100644 3rdparty/osgXR/src/OpenXR/Path.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/Path.h create mode 100644 3rdparty/osgXR/src/OpenXR/Session.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/Session.h create mode 100644 3rdparty/osgXR/src/OpenXR/Space.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/Space.h create mode 100644 3rdparty/osgXR/src/OpenXR/Swapchain.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/Swapchain.h create mode 100644 3rdparty/osgXR/src/OpenXR/SwapchainGroup.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/SwapchainGroup.h create mode 100644 3rdparty/osgXR/src/OpenXR/SwapchainGroupSubImage.h create mode 100644 3rdparty/osgXR/src/OpenXR/System.cpp create mode 100644 3rdparty/osgXR/src/OpenXR/System.h create mode 100644 3rdparty/osgXR/src/OpenXRDisplay.cpp create mode 100644 3rdparty/osgXR/src/Settings.cpp create mode 100644 3rdparty/osgXR/src/Subaction.cpp create mode 100644 3rdparty/osgXR/src/Subaction.h create mode 100644 3rdparty/osgXR/src/Version.h.in create mode 100644 3rdparty/osgXR/src/View.cpp create mode 100644 3rdparty/osgXR/src/XRFramebuffer.cpp create mode 100644 3rdparty/osgXR/src/XRFramebuffer.h create mode 100644 3rdparty/osgXR/src/XRRealizeOperation.cpp create mode 100644 3rdparty/osgXR/src/XRRealizeOperation.h create mode 100644 3rdparty/osgXR/src/XRState.cpp create mode 100644 3rdparty/osgXR/src/XRState.h create mode 100644 3rdparty/osgXR/src/XRStateCallbacks.h create mode 100644 3rdparty/osgXR/src/osgXR.cpp create mode 100644 3rdparty/osgXR/src/projection.cpp create mode 100644 3rdparty/osgXR/src/projection.h diff --git a/3rdparty/osgXR/CHANGELOG.md b/3rdparty/osgXR/CHANGELOG.md new file mode 100644 index 000000000..2213a592a --- /dev/null +++ b/3rdparty/osgXR/CHANGELOG.md @@ -0,0 +1,184 @@ +Version 0.3.7 +------------- + +New features: + * Implement basic Windows OpenGL graphics binding. + +Windows (MSVC) build fixes: + * Session: #ifdef X11 specific workaround. + * CMake: Require osgUtil. + * Manager: Avoid undefined Mirror. + * Fix Win32 DLL imports/exports. + * Subaction: Use C++11 smart pointers for \_private to avoid undefined Private + implementation class. + * Fix build against old OpenGL headers. + +ABI changes (ABI version 5): + * Subaction: Use C++11 smart pointers for \_private. + * Switch Action, ActionSet & InteractionProfile pimpls to std::unique\_ptr<>. + +Version 0.3.6 +------------- + +Bug fixes: + * Work around Monado GL context assumptions for xrCreateSession & + xrCreateSwapchain calls. + * Permit new swapchain formats: GL\_RGB10\_A2 (for Monado on AMD) & GL\_RGBA8. + +Behaviour changes: + * Report list of swapchain formats on failure to choose one. + +Behind the scenes: + * Minor whitespace cleanups in src/XRState.cpp. + +Version 0.3.5 +------------- + +Bug fixes: + * Ensure OpenXR::Action is valid before creating an action state object. + * Don't suggest empty InteractionProfile bindings to OpenXR. + +Behaviour changes: + * Manager (XRState) no longer keeps counted references to ActionSets or + InteractionProfiles. The app should manage the lifetime of these objects + itself, and can now safely discard and recreate them. + * All created Actions are now provided to OpenXR even if unreferenced by any + InteractionProfile suggested bindings. + * Action setup is now treated as a separate initialisation stage. As such if + no action sets or no interaction profiles have been created, action setup + will now take place on the next update() after they are created. + +New/expanded APIs: + * Manager::syncActionSet() - To inform osgXR that actions, action sets, or + interaction profiles have been altered, so it can take action to apply them + as soon as possible. + * ActionPose::Location::operator !=, ActionPose::Location::operator == - For + comparing pose location objects for equality. + +Behind the scenes: + * Some minor refactoring in src/Action.cpp. + +Version 0.3.4 +------------- + +Bug fixes: +* Fix draw pass accounting and slave cam VR mode. + +Behaviour changes: +* Automatically fall back from SceneView mode if the view configuration isn't + stereo. + +New/expanded APIs: +* New action APIs for exposing OpenXR actions (both input and haptics), action + sets, interaction profiles, and subactions: + * osgXR/Action: New Action, ActionBoolean, ActionFloat, ActionVector2f, + ActionPose and ActionVibration classes to represent different kinds of + XrAction. + * osgXR/ActionSet: New ActionSet class to group actions that can be activated + and deactivated together. + * osgXR/InteractionProfile: New InteractionProfile class to allows default + action bindings for interaction profiles to be suggested. + * osgXR/Subaction: New Subaction class to represent a subaction path (or top + level user path) which groups physical interactions, allowing single + actions that apply to both hands to be handled separately. + +Behind the scenes: +* Add internal action management classes in OpenXR namespace. +* Wrap XrSpace in an OpenXR::Space class. +* Wrap XrPath in an OpenXR::Path class. +* Extend inline code documentation. +* Code cleanups. + +Version 0.3.3 (formerly 0.4.0) +------------------------------ + +New/expanded APIs: +* Settings::getVisibilityMask(), Settings::getVisibilityMask() - for setting + whether osgXR should create visibility masks (when supported by the OpenXR + runtime). +* Manager::hasVisibilityMaskExtension() - for finding whether the visibility + mask extension is supported by the OpenXR runtime. +* Manager::setVisibilityMaskNodeMasks() - for setting the left and right eye + NodeMasks to use for visibility masks. + +Behind the scenes: +* Implement creation, caching, and updating of visibility mask geometry for each + OpenXR view. +* Implement rendering of visibility masks to the depth buffer to reduce fragment + overhead when GPU bound. + +Version 0.3.2 +------------- + +Bug fixes: +* Fix a couple of bugs around session recreation (used when VR or swapchain mode + changes). + +Behaviour changes: +* Pick depth buffer format based on GraphicsContext traits depth bits. +* Enable depth info submission at session state to allow it to be dynamically + switched without hitting a SteamVR hang during instance destruction. + +Behind the scenes: +* Fix a few inconsequential compiler warnings with -Wall. +* Minor cosmetic cleanups (whitespace, explicit include). + +Version 0.3.1 +------------- + +Behind the scenes: +* Fix possible crash in XRState::isRunning() if session is delayed coming up. + +Version 0.3.0 +------------- + +API changes: +* Manager::checkAndResetStateChanged() - to detect when VR state may have + changed, requiring app caches to be invalidated or synchronised with the new + state. + +Version 0.2.1 +------------- + +Behind the scenes: +* Make frame view location accessors thread safe so multiple cull threads can be + used. + +Version 0.2.0 +------------- + +API changes: +* Cleanups (moving dynamic bits out of Settings). +* Manager::update() - should be called regularly to allow for incremental + bringup and OpenXR event handling. +* Manager::setEnabled() - for triggering bringing up/down of VR. + +New/expanded APIs: +* Manager::destroyAndWait() and Manager::isDestroying() - for clean shutdown + before program exit. +* Manager::syncSettings() - to trigger appropriate reinitialisation to handle + any changed settings. +* Manager::getStateString() - to get a user readable description of the current + VR state. +* Manager::onRunning() - virtual callback when VR has started (consider setting + up desktop mirrors). +* Manager::onStopped() - virtual callback when VR has stopped (consider + removing desktop mirrors). +* Manager::onFocus() - virtual callback when VR app is running in focus + (consider resuming if paused). +* Manager::onUnfocus() - virtual callback when VR app has lost focus (consider + pausing the experience). + +Behind the scenes highlights: +* Make OpenXR bringup incremental, reversible and restartable. +* Improved handling of SteamVR's messing with GL context and threading. +* Separated event handling. + +Version 0.1.0 +------------- + +This represents early development with the API still in heavy flux. + +It supported: +* A Manager class with virtual callbacks for configuring views. +* Desktop mirrors of the VR views. diff --git a/3rdparty/osgXR/CMakeLists.txt b/3rdparty/osgXR/CMakeLists.txt new file mode 100644 index 000000000..87862f2e3 --- /dev/null +++ b/3rdparty/osgXR/CMakeLists.txt @@ -0,0 +1,79 @@ +# Top level CMakeLists.txt +cmake_minimum_required(VERSION 3.11) + +set(osgXR_MAJOR_VERSION 0) +set(osgXR_MINOR_VERSION 3) +set(osgXR_PATCH_VERSION 7) +set(osgXR_SOVERSION 5) + +set(osgXR_VERSION "${osgXR_MAJOR_VERSION}.${osgXR_MINOR_VERSION}.${osgXR_PATCH_VERSION}") + +project(osgXR + VERSION ${osgXR_VERSION} + DESCRIPTION "OpenXR integration for OpenSceneGraph applications" +) + +if(CMAKE_PROJECT_NAME STREQUAL osgXR) + # Normal top level project build, install package components + + include(CMakePackageConfigHelpers) + include(GNUInstallDirs) + + # Build options + option(BUILD_SHARED_LIBS "Whether to build as a shared library" ON) + option(BUILD_OSGXR_EXAMPLES "Enable to build osgXR examples" OFF) + + # Source files in src/ + add_subdirectory(src) + + if(BUILD_OSGXR_EXAMPLES) + add_subdirectory(examples) + endif() + + set(INSTALL_INCDIR "${CMAKE_INSTALL_INCLUDEDIR}") + + # Preprocess pkgconfig file + configure_file(osgXR.pc.in osgXR.pc @ONLY) + + # Preprocess package config + configure_package_config_file(Config.cmake.in osgXRConfig.cmake + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/osgXR" + PATH_VARS INSTALL_INCDIR + ) + # Build package version file + write_basic_package_version_file(osgXRConfigVersion.cmake + VERSION "${PROJECT_VERSION}" + COMPATIBILITY SameMinorVersion + ) + + # Install library and headers + install(TARGETS osgXR + EXPORT osgXRTargets + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + PUBLIC_HEADER DESTINATION "${INSTALL_INCDIR}/osgXR" + INCLUDES DESTINATION "${INSTALL_INCDIR}" + ) + # Install pkgconfig file + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/osgXR.pc" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig" + ) + # Install export targets + install(EXPORT osgXRTargets + FILE osgXRTargets.cmake + NAMESPACE osgXR:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/osgXR" + ) + # Install package config and version files + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/osgXRConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/osgXRConfigVersion.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/osgXR" + ) + +else() + # Subproject build + + # Just build src/ as a static library + set(osgXR_LIBRARY_TYPE STATIC) + add_subdirectory(src) + +endif() diff --git a/3rdparty/osgXR/Config.cmake.in b/3rdparty/osgXR/Config.cmake.in new file mode 100644 index 000000000..679fb6551 --- /dev/null +++ b/3rdparty/osgXR/Config.cmake.in @@ -0,0 +1,14 @@ +include(CMakeFindDependencyMacro) + +find_dependency(OpenGL) +find_dependency(OpenSceneGraph COMPONENTS osgViewer) +find_dependency(OpenXR) + +@PACKAGE_INIT@ + +set_and_check(osgXR_INCLUDE_DIR @PACKAGE_INSTALL_INCDIR@) +set(osgXR_LIBRARY osgXR::osgXR) + +include("${CMAKE_CURRENT_LIST_DIR}/osgXRTargets.cmake") + +check_required_components(osgXR) diff --git a/3rdparty/osgXR/LICENSE.txt b/3rdparty/osgXR/LICENSE.txt new file mode 100644 index 000000000..4362b4915 --- /dev/null +++ b/3rdparty/osgXR/LICENSE.txt @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/3rdparty/osgXR/README.md b/3rdparty/osgXR/README.md new file mode 100644 index 000000000..61b802ba3 --- /dev/null +++ b/3rdparty/osgXR/README.md @@ -0,0 +1,73 @@ +osgXR: Virtual Reality with OpenXR and OpenSceneGraph +===================================================== + +This library is to allow Virtual Reality support to be added to +[OpenSceneGraph](http://www.openscenegraph.org/) applications, using the +[OpenXR](https://www.khronos.org/OpenXR/) standard. + +Status: + * Still in development, contributions welcome. Plenty of work to do. + * APIs to support OpenXR VR display output in OpenSceneGraph apps. + * APIs to support OpenXR's action based input and haptic output for controller + interaction. + * OpenGL graphics bindings for Linux (X11/Xlib) and Windows (note that WMR + only supports DirectX bindings). + +License: LGPL 2.1 + +Dependencies: OpenSceneGraph, OpenXR + +Links: + * Matrix room: [#osgxr:hoganfam.uk](https://matrix.to/#/#osgxr:hoganfam.uk?via=hoganfam.uk) + * [OpenXR specifications](https://www.khronos.org/registry/OpenXR/#apispecs) + + +Installation +------------ + +Something like this: +```shell +mkdir build +cd build +cmake .. +make +make install +``` + + +Getting Started +--------------- + +To import osgXR into a CMake based project, you can use the included CMake +module, adding something like this to your CMakeLists.txt: +```cmake +find_package(osgXR 0.3.5 REQUIRED) + +target_link_libraries(target + .. + osgXR::osgXR +) +``` + +osgXR can also optionally be built as a subproject. Consider using ``git +subtree`` to import osgXR, then use something like this: +```cmake +add_subdirectory(osgXR) + +target_link_libraries(target + .. + osgXR +) +``` + +If you have installed osgXR outside of the system prefix (CMake's default prefix +on UNIX systems is ``/usr/local``), you may need to tell CMake where to find it +when you configure the project. You can do this by defining ``osgXR_DIR`` when +invoking cmake, e.g. with the argument ``-DosgXR_DIR=$PREFIX/lib/cmake/osgXR`` +where ``$PREFIX`` is osgXR's install prefix (``CMAKE_INSTALL_PREFIX``). + + +The Public API +-------------- + +See the [API documentation](docs/API.md) for details of the API. diff --git a/3rdparty/osgXR/docs/API.md b/3rdparty/osgXR/docs/API.md new file mode 100644 index 000000000..74b9e2b27 --- /dev/null +++ b/3rdparty/osgXR/docs/API.md @@ -0,0 +1,102 @@ +osgXR API Documentation +======================= + +The osgXR API is currently considered unstable, however it is versioned. Only +matching minor version numbers should be expected to be source and binary +compatible with one another. + +The osgXR headers can be found in [include/osgXR/](../include/osgXR/). + +The initial version of this library exposed ``<osgXR/OpenXRDisplay>`` for +configuring a view for VR, and ``<osgXR/osgXR>`` with a convenience wrapper +``osgXR::setupViewerDefaults`` to set up VR automatically based on environment +variables. This worked for most simple OpenSceneGraph examples, however for real +projects something more capable is needed, so it is likely these will be removed +in a future version (they are not currently working due to the new +``XRState::update()`` based state machine). + +It is instead recommended to extend the ``osgXR::Manager`` class from +``<osgXR/Manager>`` and implement the callbacks. + +## <[osgXR/Action](../include/osgXR/Action)> + +This header provides the ``osgXR::Action`` base class, and the +``osgXR::ActionBoolean``, ``osgXR::ActionFloat``, ``osgXR::ActionVector2f``, +``osgXR::ActionPose`` and ``osgXR::ActionVibration`` classes which an +application can use to define OpenXR actions, read input state, and send haptic +output. + +## <[osgXR/ActionSet](../include/osgXR/ActionSet)> + +This header provides the ``osgXR::ActionSet`` class which an application uses +to group actions into groups which can be separately activated and deactivated. + +## <[osgXR/InteractionProfile](../include/osgXR/InteractionProfile)> + +This header provides the ``osgXR::InteractionProfile`` class which an +application uses to suggest bindings between OpenXR actions and the physical +interactions of a given OpenXR controller profile. + +## <[osgXR/Manager](../include/osgXR/Manager)> + +This header provides the ``osgXR::Manager`` class which an application can +extend to implement a VR manager class. Virtual callbacks tell the application +which camera views are required to implement VR, and an ``update()`` function +gives osgXR a chance to incrementally bring up or tear down VR. + +## <[osgXR/Mirror](../include/osgXR/Mirror)> + +This header provides the ``osgXR::Mirror`` class which an application can use to +register a desktop mirror of the VR view. + +## <[osgXR/MirrorSettings](../include/osgXR/MirrorSettings)> + +This header provides the ``osgXR::MirrorSettings`` class which encapsulates +configuration data for desktop mirrors of VR views. + +## <[osgXR/OpenXRDisplay](../include/osgXR/OpenXRDisplay)> + +This header provides the ``osgXR::OpenXRDisplay`` ViewConfig class. It is +largely replaced by ``osgXR::Manager``. + +## <[osgXR/Settings](../include/osgXR/Settings)> + +This header provides the ``osgXR::Settings`` class which encapsulates all the VR +configuration data. + +## <[osgXR/Subaction](../include/osgXR/Subaction)> + +This header provides the ``osgXR::Subaction`` class which an application can +use to represent OpenXR subaction paths (top level user paths), which allow a +single OpenXR action to represent the same action on both hands. It can be +passed to other action related classes to filter actions by hand, and it can be +extended by the application to implement a callback for InteractionProfile +changes. + +## <[osgXR/View](../include/osgXR/View)> + +This header provides the ``osgXR::View`` class which represents a view of the +world which ``osgXR::Manager`` will request to be configured by the application. + +## <[osgXR/osgXR](../include/osgXR/osgXR)> + +This header provides convenience functions for quick and easy integration of VR +capabilities into a simple OpenSceneGraph application or example. It is +recommended ``osgXR::Manager`` be used instead for most projects. + +### osgXR::setupViewerDefaults() + +This sets up VR on the provided viewer based on the content of the following +environment variables: + * ``OSGXR=1`` enables VR. + * ``OSGXR_MODE=SLAVE_CAMERAS`` forces the use of separate slave cameras per view. + * ``OSGXR_MODE=SCENE_VIEW`` forces the use of OpenSceneGraph's SceneView stereo (default). + * ``OSGXR_SWAPCHAIN=MULTIPLE`` forces the use of separate swapchains per view. + * ``OSGXR_SWAPCHAIN=SINGLE`` forces the use of a single swapchain containing all views. + * ``OSGXR_UNITS_PER_METER=10`` allows the scale of the environment to be controlled. + * ``OSGXR_VALIDATION_LAYER=1`` enables the OpenXR validation layer (off by default). + * ``OSGXR_DEPTH_INFO=1`` enables passing of depth information to OpenXR (off by default). + * ``OSGXR_MIRROR=NONE`` use a blank screen as the default mirror. + * ``OSGXR_MIRROR=LEFT`` use OpenXR view 0 (left) as the default mirror. + * ``OSGXR_MIRROR=RIGHT`` use OpenXR view 1 (right) as the default mirror. + * ``OSGXR_MIRROR=LEFT_RIGHT`` use both left and right views side by side as the default mirror. diff --git a/3rdparty/osgXR/examples/CMakeLists.txt b/3rdparty/osgXR/examples/CMakeLists.txt new file mode 100644 index 000000000..4ad17295f --- /dev/null +++ b/3rdparty/osgXR/examples/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.11) +project(osgXR::examples) + +find_package(OpenGL REQUIRED) +find_package(OpenSceneGraph REQUIRED COMPONENTS osgDB osgViewer) + +if(CMAKE_PROJECT_NAME STREQUAL osgXR) + # If we're building from osgXR source tree, take a shortcut + set(osgXR_INCLUDE_DIR "../include") + set(osgXR_LIBRARY osgXR) +else() + # Otherwise, we'd normally use osgXR::osgXR, but osgXR_LIBRARY will do here + find_package(osgXR REQUIRED) +endif() + +add_executable(osgteapot osgteapot.cpp) + +target_include_directories(osgteapot + PRIVATE + ${OPENGL_INCLUDE_DIR} + ${OPENSCENEGRAPH_INCLUDE_DIRS} +) + +target_link_libraries(osgteapot + PUBLIC + ${OPENGL_LIBRARIES} + ${OPENSCENEGRAPH_LIBRARIES} + ${osgXR_LIBRARY} +) diff --git a/3rdparty/osgXR/examples/osgteapot.cpp b/3rdparty/osgXR/examples/osgteapot.cpp new file mode 100644 index 000000000..5bc071a40 --- /dev/null +++ b/3rdparty/osgXR/examples/osgteapot.cpp @@ -0,0 +1,365 @@ +/* OpenSceneGraph example, osgteapot. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +#include <osg/Geode> +#include <osg/TexGen> +#include <osg/Texture2D> + +#include <osgDB/ReadFile> + +#include <osgViewer/Viewer> + +#include <osgXR/osgXR> + + +// The classic OpenGL teapot... taken form glut-3.7/lib/glut/glut_teapot.c + +/* Copyright (c) Mark J. Kilgard, 1994. */ + +/** +(c) Copyright 1993, Silicon Graphics, Inc. + +ALL RIGHTS RESERVED + +Permission to use, copy, modify, and distribute this software +for any purpose and without fee is hereby granted, provided +that the above copyright notice appear in all copies and that +both the copyright notice and this permission notice appear in +supporting documentation, and that the name of Silicon +Graphics, Inc. not be used in advertising or publicity +pertaining to distribution of the software without specific, +written prior permission. + +THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU +"AS-IS" AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR +OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF +MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. IN NO +EVENT SHALL SILICON GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE +ELSE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT OR +CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER, +INCLUDING WITHOUT LIMITATION, LOSS OF PROFIT, LOSS OF USE, +SAVINGS OR REVENUE, OR THE CLAIMS OF THIRD PARTIES, WHETHER OR +NOT SILICON GRAPHICS, INC. HAS BEEN ADVISED OF THE POSSIBILITY +OF SUCH LOSS, HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +ARISING OUT OF OR IN CONNECTION WITH THE POSSESSION, USE OR +PERFORMANCE OF THIS SOFTWARE. + +US Government Users Restricted Rights + +Use, duplication, or disclosure by the Government is subject to +restrictions set forth in FAR 52.227.19(c)(2) or subparagraph +(c)(1)(ii) of the Rights in Technical Data and Computer +Software clause at DFARS 252.227-7013 and/or in similar or +successor clauses in the FAR or the DOD or NASA FAR +Supplement. Unpublished-- rights reserved under the copyright +laws of the United States. Contractor/manufacturer is Silicon +Graphics, Inc., 2011 N. Shoreline Blvd., Mountain View, CA +94039-7311. + +OpenGL(TM) is a trademark of Silicon Graphics, Inc. +*/ + + +/* Rim, body, lid, and bottom data must be reflected in x and + y; handle and spout data across the y axis only. */ + +static int patchdata[][16] = +{ + /* rim */ + {102, 103, 104, 105, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15}, + /* body */ + {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27}, + {24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40}, + /* lid */ + {96, 96, 96, 96, 97, 98, 99, 100, 101, 101, 101, + 101, 0, 1, 2, 3,}, + {0, 1, 2, 3, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117}, + /* bottom */ + {118, 118, 118, 118, 124, 122, 119, 121, 123, 126, + 125, 120, 40, 39, 38, 37}, + /* handle */ + {41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + 53, 54, 55, 56}, + {53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 28, 65, 66, 67}, + /* spout */ + {68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83}, + {80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95} +}; +/* *INDENT-OFF* */ + +static float cpdata[][3] = +{ + {0.2, 0, 2.7}, {0.2, -0.112, 2.7}, {0.112, -0.2, 2.7}, {0, + -0.2, 2.7}, {1.3375, 0, 2.53125}, {1.3375, -0.749, 2.53125}, + {0.749, -1.3375, 2.53125}, {0, -1.3375, 2.53125}, {1.4375, + 0, 2.53125}, {1.4375, -0.805, 2.53125}, {0.805, -1.4375, + 2.53125}, {0, -1.4375, 2.53125}, {1.5, 0, 2.4}, {1.5, -0.84, + 2.4}, {0.84, -1.5, 2.4}, {0, -1.5, 2.4}, {1.75, 0, 1.875}, + {1.75, -0.98, 1.875}, {0.98, -1.75, 1.875}, {0, -1.75, + 1.875}, {2, 0, 1.35}, {2, -1.12, 1.35}, {1.12, -2, 1.35}, + {0, -2, 1.35}, {2, 0, 0.9}, {2, -1.12, 0.9}, {1.12, -2, + 0.9}, {0, -2, 0.9}, {-2, 0, 0.9}, {2, 0, 0.45}, {2, -1.12, + 0.45}, {1.12, -2, 0.45}, {0, -2, 0.45}, {1.5, 0, 0.225}, + {1.5, -0.84, 0.225}, {0.84, -1.5, 0.225}, {0, -1.5, 0.225}, + {1.5, 0, 0.15}, {1.5, -0.84, 0.15}, {0.84, -1.5, 0.15}, {0, + -1.5, 0.15}, {-1.6, 0, 2.025}, {-1.6, -0.3, 2.025}, {-1.5, + -0.3, 2.25}, {-1.5, 0, 2.25}, {-2.3, 0, 2.025}, {-2.3, -0.3, + 2.025}, {-2.5, -0.3, 2.25}, {-2.5, 0, 2.25}, {-2.7, 0, + 2.025}, {-2.7, -0.3, 2.025}, {-3, -0.3, 2.25}, {-3, 0, + 2.25}, {-2.7, 0, 1.8}, {-2.7, -0.3, 1.8}, {-3, -0.3, 1.8}, + {-3, 0, 1.8}, {-2.7, 0, 1.575}, {-2.7, -0.3, 1.575}, {-3, + -0.3, 1.35}, {-3, 0, 1.35}, {-2.5, 0, 1.125}, {-2.5, -0.3, + 1.125}, {-2.65, -0.3, 0.9375}, {-2.65, 0, 0.9375}, {-2, + -0.3, 0.9}, {-1.9, -0.3, 0.6}, {-1.9, 0, 0.6}, {1.7, 0, + 1.425}, {1.7, -0.66, 1.425}, {1.7, -0.66, 0.6}, {1.7, 0, + 0.6}, {2.6, 0, 1.425}, {2.6, -0.66, 1.425}, {3.1, -0.66, + 0.825}, {3.1, 0, 0.825}, {2.3, 0, 2.1}, {2.3, -0.25, 2.1}, + {2.4, -0.25, 2.025}, {2.4, 0, 2.025}, {2.7, 0, 2.4}, {2.7, + -0.25, 2.4}, {3.3, -0.25, 2.4}, {3.3, 0, 2.4}, {2.8, 0, + 2.475}, {2.8, -0.25, 2.475}, {3.525, -0.25, 2.49375}, + {3.525, 0, 2.49375}, {2.9, 0, 2.475}, {2.9, -0.15, 2.475}, + {3.45, -0.15, 2.5125}, {3.45, 0, 2.5125}, {2.8, 0, 2.4}, + {2.8, -0.15, 2.4}, {3.2, -0.15, 2.4}, {3.2, 0, 2.4}, {0, 0, + 3.15}, {0.8, 0, 3.15}, {0.8, -0.45, 3.15}, {0.45, -0.8, + 3.15}, {0, -0.8, 3.15}, {0, 0, 2.85}, {1.4, 0, 2.4}, {1.4, + -0.784, 2.4}, {0.784, -1.4, 2.4}, {0, -1.4, 2.4}, {0.4, 0, + 2.55}, {0.4, -0.224, 2.55}, {0.224, -0.4, 2.55}, {0, -0.4, + 2.55}, {1.3, 0, 2.55}, {1.3, -0.728, 2.55}, {0.728, -1.3, + 2.55}, {0, -1.3, 2.55}, {1.3, 0, 2.4}, {1.3, -0.728, 2.4}, + {0.728, -1.3, 2.4}, {0, -1.3, 2.4}, {0, 0, 0}, {1.425, + -0.798, 0}, {1.5, 0, 0.075}, {1.425, 0, 0}, {0.798, -1.425, + 0}, {0, -1.5, 0.075}, {0, -1.425, 0}, {1.5, -0.84, 0.075}, + {0.84, -1.5, 0.075} +}; + +static float tex[2][2][2] = +{ + { {0, 0}, + {1, 0}}, + { {0, 1}, + {1, 1}} +}; + +/* *INDENT-ON* */ + +static void +teapot(GLint grid, GLenum type) +{ + float p[4][4][3], q[4][4][3], r[4][4][3], s[4][4][3]; + long i, j, k, l; + + glPushAttrib(GL_ENABLE_BIT | GL_EVAL_BIT); + glEnable(GL_AUTO_NORMAL); + glEnable(GL_NORMALIZE); + glEnable(GL_MAP2_VERTEX_3); + glEnable(GL_MAP2_TEXTURE_COORD_2); + for (i = 0; i < 10; i++) { + for (j = 0; j < 4; j++) { + for (k = 0; k < 4; k++) { + for (l = 0; l < 3; l++) { + p[j][k][l] = cpdata[patchdata[i][j * 4 + k]][l]; + q[j][k][l] = cpdata[patchdata[i][j * 4 + (3 - k)]][l]; + if (l == 1) + q[j][k][l] *= -1.0; + if (i < 6) { + r[j][k][l] = + cpdata[patchdata[i][j * 4 + (3 - k)]][l]; + if (l == 0) + r[j][k][l] *= -1.0; + s[j][k][l] = cpdata[patchdata[i][j * 4 + k]][l]; + if (l == 0) + s[j][k][l] *= -1.0; + if (l == 1) + s[j][k][l] *= -1.0; + } + } + } + } + glMap2f(GL_MAP2_TEXTURE_COORD_2, 0, 1, 2, 2, 0, 1, 4, 2, + &tex[0][0][0]); + glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, + &p[0][0][0]); + glMapGrid2f(grid, 0.0, 1.0, grid, 0.0, 1.0); + glEvalMesh2(type, 0, grid, 0, grid); + glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, + &q[0][0][0]); + glEvalMesh2(type, 0, grid, 0, grid); + if (i < 6) { + glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, + &r[0][0][0]); + glEvalMesh2(type, 0, grid, 0, grid); + glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, + &s[0][0][0]); + glEvalMesh2(type, 0, grid, 0, grid); + } + } + glPopAttrib(); +} + + +// Now the OSG wrapper for the above OpenGL code, the most complicated bit is computing +// the bounding box for the above example, normally you'll find this the easy bit. +class Teapot : public osg::Drawable +{ + public: + Teapot() {} + + /** Copy constructor using CopyOp to manage deep vs shallow copy.*/ + Teapot(const Teapot& teapot,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY): + osg::Drawable(teapot,copyop) {} + + META_Object(myTeapotApp,Teapot) + + + // the draw immediate mode method is where the OSG wraps up the drawing of + // of OpenGL primitives. + virtual void drawImplementation(osg::RenderInfo&) const + { + // teapot(..) doesn't use vertex arrays at all so we don't need to toggle their state + // if we did we'd need to something like following call + // state.disableAllVertexArrays(), see src/osg/Geometry.cpp for the low down. + + // just call the OpenGL code. + teapot(14,GL_FILL); + } + + + // we need to set up the bounding box of the data too, so that the scene graph knows where this + // objects is, for both positioning the camera at start up, and most importantly for culling. + virtual osg::BoundingBox computeBoundingBox() const + { + osg::BoundingBox bbox; + + // follow is some truly horrible code required to calculate the + // bounding box of the teapot. Have used the original code above to do + // help compute it. + float p[4][4][3], q[4][4][3], r[4][4][3], s[4][4][3]; + long i, j, k, l; + + for (i = 0; i < 10; i++) { + for (j = 0; j < 4; j++) { + for (k = 0; k < 4; k++) { + + for (l = 0; l < 3; l++) { + p[j][k][l] = cpdata[patchdata[i][j * 4 + k]][l]; + q[j][k][l] = cpdata[patchdata[i][j * 4 + (3 - k)]][l]; + if (l == 1) + q[j][k][l] *= -1.0; + + if (i < 6) { + r[j][k][l] = + cpdata[patchdata[i][j * 4 + (3 - k)]][l]; + if (l == 0) + r[j][k][l] *= -1.0; + s[j][k][l] = cpdata[patchdata[i][j * 4 + k]][l]; + if (l == 0) + s[j][k][l] *= -1.0; + if (l == 1) + s[j][k][l] *= -1.0; + } + } + + bbox.expandBy(osg::Vec3(p[j][k][0],p[j][k][1],p[j][k][2])); + bbox.expandBy(osg::Vec3(q[j][k][0],q[j][k][1],q[j][k][2])); + + if (i < 6) + { + bbox.expandBy(osg::Vec3(r[j][k][0],r[j][k][1],r[j][k][2])); + bbox.expandBy(osg::Vec3(s[j][k][0],s[j][k][1],s[j][k][2])); + } + + + } + } + } + + return bbox; + } + + protected: + + virtual ~Teapot() {} + +}; + + +osg::Geode* createTeapot() +{ + osg::Geode* geode = new osg::Geode(); + + // add the teapot to the geode. + geode->addDrawable( new Teapot ); + + // add a reflection map to the teapot. + osg::ref_ptr<osg::Image> image = osgDB::readRefImageFile("Images/reflect.rgb"); + if (image) + { + osg::Texture2D* texture = new osg::Texture2D; + texture->setImage(image); + + osg::TexGen* texgen = new osg::TexGen; + texgen->setMode(osg::TexGen::SPHERE_MAP); + + osg::StateSet* stateset = new osg::StateSet; + stateset->setTextureAttributeAndModes(0,texture,osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0,texgen,osg::StateAttribute::ON); + + geode->setStateSet(stateset); + } + + return geode; +} + +int main(int , char **) +{ +#if 1 + + // create viewer on heap as a test, this looks to be causing problems + // on init on some platforms, and seg fault on exit when multi-threading on linux. + // Normal stack based version below works fine though... + + // construct the viewer. + osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer; + + // add model to viewer. + viewer->setSceneData( createTeapot() ); + + osgXR::setupViewerDefaults(viewer, "osgteaport", 1); + + return viewer->run(); + +#else + + // construct the viewer. + osgViewer::Viewer viewer; + + // add model to viewer. + viewer.setSceneData( createTeapot() ); + + // create the windows and run the threads. + return viewer.run(); +#endif + +} diff --git a/3rdparty/osgXR/include/osgXR/Action b/3rdparty/osgXR/include/osgXR/Action new file mode 100644 index 000000000..108a07b91 --- /dev/null +++ b/3rdparty/osgXR/include/osgXR/Action @@ -0,0 +1,471 @@ +// -*-c++-*- +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_Action +#define OSGXR_Action 1 + +#include <osgXR/Export> + +#include <osg/Quat> +#include <osg/Referenced> +#include <osg/Vec2f> +#include <osg/Vec3f> + +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +namespace osgXR { + +class ActionSet; +class Subaction; + +/** + * Represents an OpenXR action. + * OpenXR actions are inputs & outputs which are abstracted from the physical + * input sources. The OpenXR runtime is responsible for binding them to sources, + * using suggested bindings in interaction profiles. + * + * These Action objects can persist across multiple VR sessions, and changes can + * be made at any time, however some changes won't take effect while a session + * is running. + */ +class OSGXR_EXPORT Action : public osg::Referenced +{ + public: + + class Private; + + protected: + + /// Constructor (internal). + Action(Private *priv); + + public: + + /// Destructor. + ~Action(); + + /** + * Add a subaction that may be later queried. + * Any subaction that is intended to be queried must be added to the + * action first. + * @param subaction Subaction that may be later queried. + */ + void addSubaction(Subaction *subaction); + + // Accessors + + /** + * Set the action's name and localized name. + * @param name New name for OpenXR action. + * @param localizedName A localized version of @p name. + */ + void setName(const std::string &name, + const std::string &localizedName); + + /** + * Set the action's name. + * @param name New name for OpenXR action. + */ + void setName(const std::string &name); + /// Get the action's name. + const std::string &getName() const; + + /** + * Set the action's localized name. + * @param localizedName The localized name for the action. + */ + void setLocalizedName(const std::string &localizedName); + /// Get the action's localized name. + const std::string &getLocalizedName() const; + + /** + * Get a list of currently bound source paths for this action. + * @param sourcePaths[out] Vector of source paths to write into. + */ + void getBoundSources(std::vector<std::string> &sourcePaths) const; + + typedef enum { + // Must match XR_INPUT_SOURCE_LOCALIZED_NAME_* + /// Include user path (e.g. "Left Hand"). + USER_PATH_BIT = 1, + /// Include interaction profile (e.g. "Vive Controller"). + INTERACTION_PROFILE_BIT = 2, + /// Include input component (e.g. "Trigger"). + COMPONENT_BIT = 4, + } LocalizedNameFlags; + + /** + * Get a list of currently bound source localized names for this action. + * @param whichComponents Which components to include. + * @param names[out] Vector of names to write into. + */ + void getBoundSourcesLocalizedNames(uint32_t whichComponents, + std::vector<std::string> &names) const; + + private: + + std::unique_ptr<Private> _private; + + // Copying not permitted + Action(const Action ©); +}; + +/// An action that can only have boolean values. +class OSGXR_EXPORT ActionBoolean : public Action +{ + public: + + /** + * Construct a boolean action. + * @param actionSet The action set the action should belong to. + */ + ActionBoolean(ActionSet *actionSet); + + /** + * Construct a boolean action. + * @param actionSet The action set the action should belong to. + * @param name The name of the OpenXR action, also used as the + * localized name. + */ + ActionBoolean(ActionSet *actionSet, + const std::string &name); + + /** + * Construct a boolean action. + * @param actionSet The action set the action should belong to. + * @param name The name of the OpenXR action. + * @param localizedName The localized name for the action. + */ + ActionBoolean(ActionSet *actionSet, + const std::string &name, + const std::string &localizedName); + + /** + * Get the current value of the action as a bool. + * @param subaction The subaction to filter sources from, which must + * have been specified to Action::addSubaction(). + * @return The current value of the action. + */ + bool getValue(Subaction *subaction = nullptr); +}; + +/// An action that can have floating point values. +class OSGXR_EXPORT ActionFloat : public Action +{ + public: + + /** + * Construct a floating-point action. + * @param actionSet The action set the action should belong to. + */ + ActionFloat(ActionSet *actionSet); + + /** + * Construct a floating-point action. + * @param actionSet The action set the action should belong to. + * @param name The name of the OpenXR action, also used as the + * localized name. + */ + ActionFloat(ActionSet *actionSet, + const std::string &name); + + /** + * Construct a floating-point action. + * @param actionSet The action set the action should belong to. + * @param name The name of the OpenXR action. + * @param localizedName The localized name for the action. + */ + ActionFloat(ActionSet *actionSet, + const std::string &name, + const std::string &localizedName); + + /** + * Get the current value of the action as a float. + * @param subaction The subaction to filter sources from, which must + * have been specified to Action::addSubaction(). + * @return The current value of the action. + */ + float getValue(Subaction *subaction = nullptr); +}; + +/// An action that can have 2 dimentional floating point vector values. +class OSGXR_EXPORT ActionVector2f : public Action +{ + public: + + /** + * Construct a 2d floating-point vector action. + * @param actionSet The action set the action should belong to. + */ + ActionVector2f(ActionSet *actionSet); + + /** + * Construct a 2d floating-point vector action. + * @param actionSet The action set the action should belong to. + * @param name The name of the OpenXR action, also used as the + * localized name. + */ + ActionVector2f(ActionSet *actionSet, + const std::string &name); + + /** + * Construct a 2d floating-point vector action. + * @param actionSet The action set the action should belong to. + * @param name The name of the OpenXR action. + * @param localizedName The localized name for the action. + */ + ActionVector2f(ActionSet *actionSet, + const std::string &name, + const std::string &localizedName); + + /** + * Get the current value of the action as an OSG vector. + * @param subaction The subaction to filter sources from, which must + * have been specified to Action::addSubaction(). + * @return The current value of the action. + */ + osg::Vec2f getValue(Subaction *subaction = nullptr); +}; + +/// An action that can have pose (position and orientation) values. +class OSGXR_EXPORT ActionPose : public Action +{ + public: + + /** + * Construct a pose action. + * @param actionSet The action set the action should belong to. + */ + ActionPose(ActionSet *actionSet); + + /** + * Construct a pose action. + * @param actionSet The action set the action should belong to. + * @param name The name of the OpenXR action, also used as the + * localized name. + */ + ActionPose(ActionSet *actionSet, + const std::string &name); + + /** + * Construct a pose action. + * @param actionSet The action set the action should belong to. + * @param name The name of the OpenXR action. + * @param localizedName The localized name for the action. + */ + ActionPose(ActionSet *actionSet, + const std::string &name, + const std::string &localizedName); + + /** + * Represents a pose action's position and orientation. + * This represents a pose action's position and orientation, along with + * flags to indicate whether each of these are valid and whether they're + * currently tracked (as opposed to estimated based on recent tracking). + */ + class OSGXR_EXPORT Location + { + public: + + typedef enum { + // Must match XR_SPACE_LOCATION_* */ + ORIENTATION_VALID_BIT = 0x1, + POSITION_VALID_BIT = 0x2, + ORIENTATION_TRACKED_BIT = 0x4, + POSITION_TRACKED_BIT = 0x8, + } Flags; + + // Constructors + + /// Construct a pose action location. + Location(); + /// Construct a pose action location. + Location(Flags flags, + const osg::Quat &orientation, + const osg::Vec3f &position); + + // Accessors + + /** + * Find whether the orientation is valid. + * If not, the orientation is undefined. + * @return Whether the orientation is valid. + */ + bool isOrientationValid() const + { + return _flags & ORIENTATION_VALID_BIT; + } + + /** + * Find whether the position is valid. + * If not, the position is undefined. + * @return Whether the position is valid. + */ + bool isPositionValid() const + { + return _flags & POSITION_VALID_BIT; + } + + /** + * Find whether the orientation is being tracked. + * If not, the orientation may only be an estimate. + * @return Whether the orientation is being tracked. + */ + bool isOrientationTracked() const + { + return _flags & ORIENTATION_TRACKED_BIT; + } + + /** + * Find whether the position is being tracked. + * If not, the position may only be an estimate. + * @return Whether the position is being tracked. + */ + bool isPositionTracked() const + { + return _flags & POSITION_TRACKED_BIT; + } + + /// Get the flags which indicate validity and tracking. + Flags getFlags() const + { + return _flags; + } + + /** + * Get the pose action's orientation as a quaternion. + * Get the pose action's orientation relative to the default + * reference space as an OSG quaternion. + * + * The orientation is undefined if isOrientationValid() returns + * false. + * + * The orientation may only be an estimate if + * isOrientationTracked() returns false. + */ + const osg::Quat &getOrientation() const + { + return _orientation; + } + + /** + * Get the pose action's position as a 3D vector. + * Get the pose action's position relative to the default + * reference space as an OSG 3D vector. + * + * The position is undefined if isPositionValid() returns false. + * + * The position may only be an estimate if isPositionTracked() + * returns false. + */ + const osg::Vec3f &getPosition() const + { + return _position; + } + + // Comparison operators + + bool operator != (const Location &other) const + { + return _flags != other._flags || + (isOrientationValid() && _orientation != other._orientation) || + (isPositionValid() && _position != other._position); + } + + bool operator == (const Location &other) const + { + return !operator != (other); + } + + protected: + + Flags _flags; + osg::Quat _orientation; + osg::Vec3f _position; + }; + + /** + * Get the current pose of the action as a Location object. + * @param subaction The subaction to filter sources from, which must + * have been specified to Action::addSubaction(). + * @return The current pose of the action. + */ + Location getValue(Subaction *subaction = nullptr); +}; + +/// An output action for vibration. +class OSGXR_EXPORT ActionVibration : public Action +{ + public: + + /** + * Construct a vibration output action. + * @param actionSet The action set the action should belong to. + */ + ActionVibration(ActionSet *actionSet); + + /** + * Construct a vibration output action. + * @param actionSet The action set the action should belong to. + * @param name The name of the OpenXR action, also used as the + * localized name. + */ + ActionVibration(ActionSet *actionSet, + const std::string &name); + + /** + * Construct a vibration output action. + * @param actionSet The action set the action should belong to. + * @param name The name of the OpenXR action. + * @param localizedName The localized name for the action. + */ + ActionVibration(ActionSet *actionSet, + const std::string &name, + const std::string &localizedName); + + enum { + /// Indicates a minimum supported durection for a haptic device. + DURATION_MIN = -1, + /// Indicates an optimal frequency for a haptic pulse. + FREQUENCY_UNSPECIFIED = 0, + }; + + /** + * Apply haptic feedback. + * @param duration_ns Duration of vibration in nanoseconds. + * @param frequency Frequency of vibration in Hz. + * @param amplitude Amplitude of vibration between 0.0 and 1.0. + * @return true on success, false otherwise. + */ + bool applyHapticFeedback(int64_t duration_ns, float frequency, + float amplitude); + + /** + * Apply haptic feedback. + * @param subaction The subaction to apply haptics to, which must + * have been specified to Action::addSubaction(). + * @param duration_ns Duration of vibration in nanoseconds. + * @param frequency Frequency of vibration in Hz. + * @param amplitude Amplitude of vibration between 0.0 and 1.0. + * @return true on success, false otherwise. + */ + bool applyHapticFeedback(Subaction *subaction, + int64_t duration_ns, float frequency, + float amplitude); + + /** + * Stop any in-progress haptic feedback. + * @param subaction The subaction to apply haptics to, which must + * have been specified to Action::addSubaction(). + * @return true on success, false otherwise. + */ + bool stopHapticFeedback(Subaction *subaction = nullptr); +}; + +} + +#endif diff --git a/3rdparty/osgXR/include/osgXR/ActionSet b/3rdparty/osgXR/include/osgXR/ActionSet new file mode 100644 index 000000000..63bb9eb51 --- /dev/null +++ b/3rdparty/osgXR/include/osgXR/ActionSet @@ -0,0 +1,138 @@ +// -*-c++-*- +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_ActionSet +#define OSGXR_ActionSet 1 + +#include <osgXR/Export> + +#include <osg/Referenced> + +#include <cstdint> +#include <memory> +#include <string> + +namespace osgXR { + +class Manager; +class Subaction; + +/** + * Represents a group of OpenXR actions. + * Action sets are attached to the OpenXR session, and can be dynamically + * activated and deactivated. + */ +class OSGXR_EXPORT ActionSet : public osg::Referenced +{ + public: + + /** + * Construct an action set. + * @param manager The VR manager object to add the action set to. + */ + ActionSet(Manager *manager); + + /** + * Construct an action set. + * @param manager The VR manager object to add the action set to. + * @param name The name of the OpenXR action set, also used as the + * localized name. + */ + ActionSet(Manager *manager, + const std::string &name); + + /** + * Construct an action set. + * @param manager The VR manager object to add the action set to. + * @param name The name of the OpenXR action set. + * @param localizedName The localized name for the action set. + */ + ActionSet(Manager *manager, + const std::string &name, + const std::string &localizedName); + + /// Destructor. + ~ActionSet(); + + // Accessors + + /** + * Set the action set's name and localized name. + * @param name New name for OpenXR action set. + * @param localizedName A localized version of @p name. + */ + void setName(const std::string &name, + const std::string &localizedName); + + /** + * Set the action set's name. + * @param name New name for OpenXR action set. + */ + void setName(const std::string &name); + /// Get the action's name. + const std::string &getName() const; + + /** + * Set the action set's localized name. + * @param localizedName The localized name for the action set. + */ + void setLocalizedName(const std::string &localizedName); + /// Get the action set's localized name. + const std::string &getLocalizedName() const; + + /** + * Set the priority of the action set. + * @param priority New priority of the action set. Larger priority + * action sets take precedence over smaller priority + * action sets. + */ + void setPriority(uint32_t priority); + /// Get the priority of the action set. + uint32_t getPriority() const; + + // Activation of the action set + + /** + * Activate the action set within a subaction. + * Set the action set as active so that its actions (filtered by + * subaction) are synchronised each frame. If @p subaction is nullptr, + * all subactions in the set will be synchronised, otherwise multiple + * subactions can be activated by multiple calls. + * @param subaction The subaction to activate this action set within. + * May be nullptr (default) in which case all + * subactions are activated. + */ + void activate(Subaction *subaction = nullptr); + + /** + * Deactivate the action set within a subaction. + * Set the action set as inactive so that its actions (filtered by + * subaction) are no longer synchronised each frame. If @p subaction is + * nullptr, any full activation is removed, otherwise multiple + * subactions can be deactivated by multiple calls. + * @param subaction The subaction to deactivate this action set within. + * May be nullptr (default) in which case all + * subactions activations are removed. + */ + void deactivate(Subaction *subaction = nullptr); + + /** + * Find whether the action set is activated for any subactions. + * @return Whether any subactions are activated for this action set. + */ + bool isActive(); + + class Private; + + private: + + std::unique_ptr<Private> _private; + + // Copying not permitted + ActionSet(const ActionSet ©); +}; + +} + +#endif diff --git a/3rdparty/osgXR/include/osgXR/Export b/3rdparty/osgXR/include/osgXR/Export new file mode 100644 index 000000000..ab58b4c28 --- /dev/null +++ b/3rdparty/osgXR/include/osgXR/Export @@ -0,0 +1,22 @@ +// -*-c++-*- +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_Export +#define OSGXR_Export 1 + +#include <osgXR/Config> + +#if defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__) + #if defined(OSGXR_STATIC_LIBRARY) + #define OSGXR_EXPORT + #elif defined(OSGXR_LIBRARY) + #define OSGXR_EXPORT __declspec(dllexport) + #else + #define OSGXR_EXPORT __declspec(dllimport) + #endif +#else + #define OSGXR_EXPORT +#endif + +#endif diff --git a/3rdparty/osgXR/include/osgXR/InteractionProfile b/3rdparty/osgXR/include/osgXR/InteractionProfile new file mode 100644 index 000000000..71b9d4e71 --- /dev/null +++ b/3rdparty/osgXR/include/osgXR/InteractionProfile @@ -0,0 +1,74 @@ +// -*-c++-*- +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_InteractionProfile +#define OSGXR_InteractionProfile 1 + +#include <osgXR/Export> + +#include <osg/Referenced> + +#include <memory> +#include <string> + +namespace osgXR { + +class Action; +class Manager; + +/** + * Represents a group of suggested bindings for a specific interaction profile. + * This class allow the application to suggest bindings for actions to specific + * input paths for a given interaction profile. If the OpenXR runtime recognises + * the profile it may use the suggested bindings to bind actions to whichever + * input devices the user may have, even without a specific binding to that + * device. + */ +class OSGXR_EXPORT InteractionProfile : public osg::Referenced +{ + public: + + /** + * Construct an interaction profile. + * The OpenXR interaction profile path is constructed as + * "/interaction_profiles/@p vendor /@p type ". + * @param manager The VR manager object to add the action set to. + * @param vendor Vendor segment of OpenXR interaction profile path. + * @param type Type segment of OpenXR interaction profile path. + */ + InteractionProfile(Manager *manager, + const std::string &vendor, + const std::string &type); + + /// Destructor + ~InteractionProfile(); + + // Accessors + + /// Get the vendor segment of the OpenXR interaction profile path. + const std::string &getVendor() const; + + /// Get the type segment of the OpenXR interaction profile path. + const std::string &getType() const; + + /** + * Suggest a binding for an action. + * @param action The action to bind. + * @param binding The OpenXR path to bind the action to. + */ + void suggestBinding(Action *action, const std::string &binding); + + class Private; + + private: + + std::unique_ptr<Private> _private; + + // Copying not permitted + InteractionProfile(const InteractionProfile ©); +}; + +} + +#endif diff --git a/3rdparty/osgXR/include/osgXR/Manager b/3rdparty/osgXR/include/osgXR/Manager new file mode 100644 index 000000000..bbd53819a --- /dev/null +++ b/3rdparty/osgXR/include/osgXR/Manager @@ -0,0 +1,237 @@ +// -*-c++-*- +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_Manager +#define OSGXR_Manager 1 + +#include <osg/Camera> +#include <osg/Node> +#include <osg/ref_ptr> + +#include <osgViewer/View> +#include <osgViewer/ViewerBase> + +#include <osgXR/Export> +#include <osgXR/Mirror> +#include <osgXR/Settings> +#include <osgXR/View> + +#include <list> + +namespace osgXR { + +// Internal state class +class XRState; + +/** + * Public VR state manager class. + * Applications can extend this class to allow tighter integration with osgXR. + */ +class OSGXR_EXPORT Manager : public osgViewer::ViewConfig +{ + public: + + Manager(); + virtual ~Manager(); + + /// Use if viewer is a CompositeViewer. + void setViewer(osgViewer::ViewerBase *viewer) + { + _viewer = viewer; + } + + /// Set the NodeMasks to use for visibility masks. + void setVisibilityMaskNodeMasks(osg::Node::NodeMask left, + osg::Node::NodeMask right) const; + + void configure(osgViewer::View& view) const override; + + /** + * Perform a regular update. + * This will poll for OpenXR events, and handle any pending VR start / + * stop operations (possibly invoking the Manager's view callbacks). + * Some of these operations require threading on the viewer to be + * temporarily stopped, but in all cases it is started again. + */ + virtual void update(); + + /// Find whether state has changed since last call, and reset. + bool checkAndResetStateChanged(); + + /// Find whether VR seems to be present. + bool getPresent() const; + + /** + * Get whether VR is currently set to be enabled. + * When enabled, osgXR will try to keep VR running. + * @return Whether VR is enabled + */ + bool getEnabled() const; + /** + * Set whether VR is currently set to be enabled. + * When enabled, osgXR will try to keep VR running. + * @param enabled Whether VR is enabled. + */ + void setEnabled(bool enabled); + + /** + * Start destroying the VR state and wait for safe shutdown. + */ + void destroyAndWait(); + + /** + * Find whether this manager is in the process of being destroyed. + */ + bool isDestroying() const; + + /** + * Get whether a VR session is currently running. + * @return Whether a VR session is currently running. + */ + bool isRunning() const; + + /// Arrange reinit as needed for new settings. + void syncSettings(); + + /// Arrange reinit as needed of action setup. + void syncActionSetup(); + + /* + * OpenXR information. + */ + + /** + * Find whether OpenXR's validation layer is supported. + * This looks to see whether the OpenXR validation API layer (i.e. + * XR_APILAYER_LUNARG_core_validation) is available. + */ + bool hasValidationLayer() const; + + /** + * Find whether OpenXR supports the submission of depth information. + * This looks to see whether the OpenXR instance extension for + * submitting depth information to help the runtime perform better + * reprojection (i.e. XR_KHR_composition_layer_depth) is available. + */ + bool hasDepthInfoExtension() const; + + /** + * Find whether OpenXR supports the visibility mask extension. + * This looks to see whether the OpenXR instance extension for getting + * visibility masks is available, which can be used to reduce fragment + * load. + */ + bool hasVisibilityMaskExtension() const; + + /// Find the name of the OpenXR runtime. + const char *getRuntimeName() const; + + /// Find the name of the OpenXR system in use. + const char *getSystemName() const; + + /// Get a string describing the state (for user consumption). + const char *getStateString() const; + + /* + * For implementation by derived classes. + */ + + /** + * Callback telling the app to configure a new view. + * This callback allows osgXR to tell the app to configure a new view of + * the world. The application should notify osgXR of the addition and + * removal of slave cameras which osgXR should hook into using the + * osgXR::View parameter. + * The implementation may stop threading, and it will be started again + * before update() returns. + * @param xrView The new osgXR::View with a public API to allow the + * application to retrieve what it needs in relation to + * the view and to inform osgXR of changes. + */ + virtual void doCreateView(View *xrView) = 0; + + /** + * Callback telling the app to destroy an existing view. + * This callback allows osgXR to tell the app to remove an existing view + * of the world that it had requested via doCreateView(). The + * application should notify osgXR of the removal of any slave cameras + * which it has already informed osgXR about. + * The implementation may stop threading, and it will be started again + * before update() returns. + */ + virtual void doDestroyView(View *xrView) = 0; + + /** + * Callback telling the app that the VR session is now running. + * This happens after the OpenXR session has started running, and views + * have been configured (see doCreateView()). The app should start + * rendering the VR views, and may choose to reconfigure the desktop + * window to make a VR mirror visible. + */ + virtual void onRunning(); + + /** + * Callback telling the app that the VR session has now stopped. + * This happens after the OpenXR session has stopped, and views have + * been removed (see doDestroyView()). The app should stop rendering the + * VR views, and may choose to reconfigure the desktop window so as to + * no longer show a VR mirror. + */ + virtual void onStopped(); + + /** + * Callback telling the app that the VR session is in focus. + * This happens when the VR session enters focus and can get VR input + * from the user. The app may choose to resume the experience if it was + * previously paused due to onUnfocus(). + */ + virtual void onFocus(); + + /** + * Callback telling the app that the VR session is no longer in focus. + * This happens when the VR session leaves focus and can no longer get + * VR input from the user. The VR runtime may be presenting a modal + * pop-up on top of the application's rendered frames. The app may + * choose to pause the experience. + */ + virtual void onUnfocus(); + + + /// Add a custom mirror to the queue of mirrors to configure. + void addMirror(Mirror *mirror); + + /// Set up a camera to render a VR mirror. + void setupMirrorCamera(osg::Camera *camera); + + /* + * Internal + */ + + inline Settings *_getSettings() + { + return _settings.get(); + } + + inline XRState *_getXrState() + { + return _state; + } + + void _setupMirrors(); + + protected: + + osg::ref_ptr<osgViewer::ViewerBase> _viewer; + osg::ref_ptr<Settings> _settings; + bool _destroying; + + private: + + std::list<osg::ref_ptr<Mirror> > _mirrorQueue; + osg::ref_ptr<XRState> _state; +}; + +} + +#endif diff --git a/3rdparty/osgXR/include/osgXR/Mirror b/3rdparty/osgXR/include/osgXR/Mirror new file mode 100644 index 000000000..ce0724e22 --- /dev/null +++ b/3rdparty/osgXR/include/osgXR/Mirror @@ -0,0 +1,50 @@ +// -*-c++-*- +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_Mirror +#define OSGXR_Mirror 1 + +#include <osg/Camera> +#include <osg/Referenced> +#include <osg/ref_ptr> + +#include <osgXR/Export> +#include <osgXR/MirrorSettings> + +namespace osgXR { + +class Manager; + +/** + * Public VR mirror class. + */ +class OSGXR_EXPORT Mirror : public osg::Referenced +{ + public: + + Mirror(Manager *manager, osg::Camera *camera); + virtual ~Mirror(); + + /* + * internal + */ + + // Called when enough is known about OpenXR system + void _init(); + + private: + + void setupQuad(unsigned int viewIndex, + float x, float w); + + osg::observer_ptr<Manager> _manager; + osg::observer_ptr<osg::Camera> _camera; + + MirrorSettings _mirrorSettings; +}; + +} + +#endif + diff --git a/3rdparty/osgXR/include/osgXR/MirrorSettings b/3rdparty/osgXR/include/osgXR/MirrorSettings new file mode 100644 index 000000000..1ad315e30 --- /dev/null +++ b/3rdparty/osgXR/include/osgXR/MirrorSettings @@ -0,0 +1,72 @@ +// -*-c++-*- +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_MirrorSettings +#define OSGXR_MirrorSettings 1 + +#include <osgXR/Export> + +namespace osgXR { + +class OSGXR_EXPORT MirrorSettings +{ + public: + + MirrorSettings(); + + /// Equality operator. + bool operator == (const MirrorSettings &other) const + { + return _mirrorMode == other._mirrorMode && + (_mirrorMode != MIRROR_SINGLE || + _mirrorViewIndex == other._mirrorViewIndex); + } + + /// Inequality operator. + bool operator != (const MirrorSettings &other) const + { + return _mirrorMode != other._mirrorMode || + (_mirrorMode == MIRROR_SINGLE && + _mirrorViewIndex != other._mirrorViewIndex); + } + + /// Type of VR mirror to show. + typedef enum MirrorMode + { + /// Choose automatically. + MIRROR_AUTOMATIC, + /// Render nothing to the mirror. + MIRROR_NONE, + /// Render a single view fullscreen to the mirror. + MIRROR_SINGLE, + /// Render left & right views side by side. + MIRROR_LEFT_RIGHT, + } MirrorMode; + /// Set the mirror mode to use. + void setMirror(MirrorMode mode, int viewIndex = -1) + { + _mirrorMode = mode; + _mirrorViewIndex = viewIndex; + } + /// Get the mirror mode to use. + MirrorMode getMirrorMode() const + { + return _mirrorMode; + } + /// Get the mirror view index. + int getMirrorViewIndex() const + { + return _mirrorViewIndex; + } + + protected: + + // Mirror mode + MirrorMode _mirrorMode; + int _mirrorViewIndex; +}; + +} + +#endif diff --git a/3rdparty/osgXR/include/osgXR/OpenXRDisplay b/3rdparty/osgXR/include/osgXR/OpenXRDisplay new file mode 100644 index 000000000..6639e92c6 --- /dev/null +++ b/3rdparty/osgXR/include/osgXR/OpenXRDisplay @@ -0,0 +1,49 @@ +// -*-c++-*- +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OpenXRDisplay +#define OSGXR_OpenXRDisplay 1 + +#include <osgXR/Export> +#include <osgXR/Settings> + +#include <osg/Referenced> +#include <osg/ref_ptr> +#include <osgViewer/View> + +#include <cinttypes> +#include <string> + +namespace osgXR { + +class XRState; + +/** a camera for each OpenXR view.*/ +class OSGXR_EXPORT OpenXRDisplay : public osgViewer::ViewConfig +{ + public: + + OpenXRDisplay(); + OpenXRDisplay(Settings *settings); + + OpenXRDisplay(const OpenXRDisplay& rhs, + const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); + virtual ~OpenXRDisplay(); + + META_Object(osgXR, OpenXRDisplay); + + void configure(osgViewer::View& view) const override; + + protected: + + osg::ref_ptr<Settings> _settings; + + // Internal OpenXR state object + // FIXME this should probably belong elsewhere + mutable osg::ref_ptr<XRState> _state; +}; + +} + +#endif diff --git a/3rdparty/osgXR/include/osgXR/Settings b/3rdparty/osgXR/include/osgXR/Settings new file mode 100644 index 000000000..a413492f9 --- /dev/null +++ b/3rdparty/osgXR/include/osgXR/Settings @@ -0,0 +1,345 @@ +// -*-c++-*- +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_Settings +#define OSGXR_Settings 1 + +#include <osg/Referenced> + +#include <osgXR/Export> +#include <osgXR/MirrorSettings> + +#include <string> + +namespace osgXR { + +/// Encapsulates osgXR / OpenXR settings data. +class OSGXR_EXPORT Settings : public osg::Referenced +{ + public: + + /* + * Instance management. + */ + + Settings(); + virtual ~Settings(); + + /// Get the default/global instance of Settings. + static Settings *instance(); + + /* + * OpenXR application information. + */ + + /** + * Set the application's name and version to expose to OpenXR. + * These will be used to create an OpenXR instance. + * @param appName Name of the application. + * @param appVersion 32-bit version number of the application. + */ + void setApp(const std::string &appName, uint32_t appVersion) + { + _appName = appName; + _appVersion = appVersion; + } + + /** + * Set the application's name to expose to OpenXR. + * This will be used to create an OpenXR instance. + * @param appName Name of the application. + */ + void setAppName(const std::string &appName) + { + _appName = appName; + } + /// Get the application's name to expose to OpenXR. + const std::string &getAppName() const + { + return _appName; + } + + /** + * Set the application's version to expose to OpenXR. + * This will be used to create an OpenXR instance. + * @param appVersion 32-bit version number of the application. + */ + void setAppVersion(uint32_t appVersion) + { + _appVersion = appVersion; + } + /// Get the application's 32-bit version number to expose to OpenXR. + uint32_t getAppVersion() const + { + return _appVersion; + } + + + /* + * osgXR configuration settings. + */ + + /** + * Set whether to try enabling OpenXR's validation layer. + * This controls whether the OpenXR validation API layer (i.e. + * XR_APILAYER_LUNARG_core_validation) will be enabled when creating an + * OpenXR instance. + * By default this is disabled. + * @param validationLayer Whether to try enabling the validation layer. + */ + void setValidationLayer(bool validationLayer) + { + _validationLayer = validationLayer; + } + /// Get whether to try enabling OpenXR's validation layer. + bool getValidationLayer() const + { + return _validationLayer; + } + + /** + * Set whether to enable submission of depth information to OpenXR. + * This controls whether the OpenXR instance depth information extension + * (i.e XR_KHR_composition_layer_depth) will be used to submit depth + * information to OpenXR to allow improved reprojection. + * This is currently disabled by default. + * @param depthInfo Whether to enable submission of depth information. + */ + void setDepthInfo(bool depthInfo) + { + _depthInfo = depthInfo; + } + /// Get whether to enable submission of depth information to OpenXR. + bool getDepthInfo() const + { + return _depthInfo; + } + + /** + * Set whether to create visibility masks. + * This controls whether the OpenXR instance visibility mask extension + * (i.e. XR_KHR_visibility_mask) will be used to create and update + * visibility masks for each VR view in order to mask hidden fragments. + * This is enabled by default. + * @param visibilityMask Whether to create visibility masks. + */ + void setVisibilityMask(bool visibilityMask) + { + _visibilityMask = visibilityMask; + } + /// Get whether to create visibility masks. + bool getVisibilityMask() const + { + return _visibilityMask; + } + + /// OpenXR system orm factors. + typedef enum FormFactor + { + /// A display mounted to the user's head. + HEAD_MOUNTED_DISPLAY, + /// A display held in the user's hands. + HANDHELD_DISPLAY, + } FormFactor; + /** + * Set which OpenXR form factor to use. + * This controls which OpenXR form factor to try to use. The default is + * HEAD_MOUNTED_DISPLAY. + * @param formFactor Form factor to use. + */ + void setFormFactor(FormFactor formFactor) + { + _formFactor = formFactor; + } + /// Get which OpenXR form factor to use. + FormFactor getFormFactor() const + { + return _formFactor; + } + + /// Modes for blending layers onto the user's view of the real world. + typedef enum BlendMode + { + // Matches XrEnvironmentBlendMode + /// Display layers with no view of physical world behind. + OPAQUE = 1, + /// Additively blend layers with view of physical world behind. + ADDITIVE = 2, + /// Alpha blend layers with view of physical world behind. + ALPHA_BLEND = 3, + } BlendMode; + /** + * Specify a preferred environment blend mode. + * The chosen environment blend mode is permitted for use, and will be + * chosen in preference to any other supported environment blend modes + * specified by allowEnvBlendMode() if supported by OpenXR. + * @param mode Environment blend mode to prefer. + */ + void preferEnvBlendMode(BlendMode mode) + { + uint32_t mask = (1u << (unsigned int)mode); + _preferredEnvBlendModeMask |= mask; + _allowedEnvBlendModeMask |= mask; + } + /** + * Specify a permitted environment blend mode. + * The chosen environment blend mode is permitted for use, and may be + * chosen if supported by OpenXR when none of the preferred environment + * blend modes specified by preferEnvBlenMode() are supported by OpenXR. + * @param mode Environment blend mode to prefer. + */ + void allowEnvBlendMode(BlendMode mode) + { + uint32_t mask = (1u << (unsigned int)mode); + _allowedEnvBlendModeMask |= mask; + } + /// Get the bitmask of preferred environment blend modes. + uint32_t getPreferredEnvBlendModeMask() const + { + return _preferredEnvBlendModeMask; + } + /// Set the bitmask of preferred environment blend modes. + void setPreferredEnvBlendModeMask(uint32_t preferredEnvBlendModeMask) + { + _preferredEnvBlendModeMask = preferredEnvBlendModeMask; + } + /// Get the bitmask of permitted environment blend modes. + uint32_t getAllowedEnvBlendModeMask() const + { + return _allowedEnvBlendModeMask; + } + /// Set the bitmask of allowed environment blend modes. + void setAllowedEnvBlendModeMask(uint32_t allowedEnvBlendModeMask) + { + _allowedEnvBlendModeMask = allowedEnvBlendModeMask; + } + + /// Techniques for rendering multiple views. + typedef enum VRMode + { + /// Choose automatically. + VRMODE_AUTOMATIC, + /** Create a slave camera for each view. + * Either separate swapchains, or single with multiple viewports. + */ + VRMODE_SLAVE_CAMERAS, + /** Use the OSG SceneView stereo functionality. + * No extra slave cameras. + * Only supports SWAPCHAIN_SINGLE with stereo. + */ + VRMODE_SCENE_VIEW, + } VRMode; + /// Set the rendering technique to use. + void setVRMode(VRMode mode) + { + _vrMode = mode; + } + /// Get the rendering technique to use. + VRMode getVRMode() const + { + return _vrMode; + } + + /// Techniques for managing swapchains. + typedef enum SwapchainMode + { + /// Choose automatically. + SWAPCHAIN_AUTOMATIC, + /// Create a 2D swapchain per view. + SWAPCHAIN_MULTIPLE, + /** Create a single 2D swapchain with a viewport per view. + * Stack them horizontally. + */ + SWAPCHAIN_SINGLE, + } SwapchainMode; + /// Set the swapchain management technique to use. + void setSwapchainMode(SwapchainMode mode) + { + _swapchainMode = mode; + } + /// Get the swapchain management technique to use. + SwapchainMode getSwapchainMode() const + { + return _swapchainMode; + } + + /// Get mirror settings. + MirrorSettings &getMirrorSettings() + { + return _mirrorSettings; + } + /// Get mirror settings. + const MirrorSettings &getMirrorSettings() const + { + return _mirrorSettings; + } + + /** + * Set the number of virtual world units to fit per real world meter. + * This controls the size of the user relative to the virtual world, by + * scaling down the size of the world. + * @param unitsPerMeter The number of units per real world meter. + */ + void setUnitsPerMeter(float unitsPerMeter) + { + _unitsPerMeter = unitsPerMeter; + } + /// Get the number of virtual world units to fit per real world meter. + float getUnitsPerMeter() const + { + return _unitsPerMeter; + } + + // Internal APIs + + typedef enum { + DIFF_NONE = 0, + DIFF_APP_INFO = (1u << 0), + DIFF_VALIDATION_LAYER = (1u << 1), + DIFF_DEPTH_INFO = (1u << 2), + DIFF_VISIBILITY_MASK = (1u << 3), + DIFF_FORM_FACTOR = (1u << 4), + DIFF_BLEND_MODE = (1u << 5), + DIFF_VR_MODE = (1u << 6), + DIFF_SWAPCHAIN_MODE = (1u << 7), + DIFF_MIRROR = (1u << 8), + DIFF_SCALE = (1u << 9), + } _ChangeMask; + + unsigned int _diff(const Settings &other) const; + + private: + + /* + * Internal data. + */ + + // For XrInstance creation + std::string _appName; + uint32_t _appVersion; + bool _validationLayer; + bool _depthInfo; + bool _visibilityMask; + + // To get XrSystem + FormFactor _formFactor; + + // For choosing environment blend mode + uint32_t _preferredEnvBlendModeMask; + uint32_t _allowedEnvBlendModeMask; + + // VR/swapchain modes to use + VRMode _vrMode; + SwapchainMode _swapchainMode; + + // Mirror settings + MirrorSettings _mirrorSettings; + + // How big the world + float _unitsPerMeter; +}; + +} + +#endif diff --git a/3rdparty/osgXR/include/osgXR/Subaction b/3rdparty/osgXR/include/osgXR/Subaction new file mode 100644 index 000000000..fc9d9e00a --- /dev/null +++ b/3rdparty/osgXR/include/osgXR/Subaction @@ -0,0 +1,75 @@ +// -*-c++-*- +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_Subaction +#define OSGXR_Subaction 1 + +#include <osgXR/Export> + +#include <osg/Referenced> +#include <osg/ref_ptr> + +#include <memory> +#include <string> + +namespace osgXR { + +class InteractionProfile; +class Manager; + +/** + * Represents an OpenXR subaction path (a.k.a top level user path). + * This represents an OpenXR subaction path, also referred to in the OpenXR spec + * as a top level user path. These are the physical groupings of inputs, for + * example "/user/head" or "/user/hand/left". Actions and action sets can be + * filtered by subactions, so that the same action (e.g. "shoot") can be read + * separately for different hands. + */ +class OSGXR_EXPORT Subaction : public osg::Referenced +{ + public: + + /** + * Construct a subaction for a path. + * @param manager The VR manager object to add the action set to. + * @param path The subaction path, e.g. "/user/hand/left". + */ + Subaction(Manager *manager, + const std::string &path); + + /// Destructor + virtual ~Subaction(); + + // Accessors + + /// Get the subaction's path. + const std::string &getPath() const; + + /// Find the interaction profile bound to the subaction. + InteractionProfile *getCurrentProfile(); + + class Private; + + protected: + + // Change handlers + + /** + * Notification of change of interaction profile for subaction. + * This is called when the subaction's current interaction profile is + * changed. Derived classes can implement this to their own ends. + * @param newProfile The interaction profile object that is now + * current for the subaction indicated by @p + * subaction. May be nullptr. + */ + virtual void onProfileChanged(InteractionProfile *newProfile); + + private: + + std::shared_ptr<Private> _private; +}; + +} + +#endif diff --git a/3rdparty/osgXR/include/osgXR/View b/3rdparty/osgXR/include/osgXR/View new file mode 100644 index 000000000..1e227c1b2 --- /dev/null +++ b/3rdparty/osgXR/include/osgXR/View @@ -0,0 +1,79 @@ +// -*-c++-*- +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_View +#define OSGXR_View 1 + +#include <osgXR/Export> + +#include <osg/Camera> +#include <osg/ref_ptr> + +#include <osgViewer/GraphicsWindow> +#include <osgViewer/View> + +namespace osgXR { + +/** + * Representation of a view from an osgXR app point of view. + * This represents a render view that osgXR expects the application to set up. + * This may not directly correspond to OpenXR views, for example if using stereo + * SceneView mode there will be a single view set up for stereo rendering. + */ +class OSGXR_EXPORT View : public osg::Referenced +{ + public: + + /* + * Application -> osgXR notifications. + */ + + /** + * Notify osgXR that a new slave camera has been added to the view. + * This tells osgXR that a new slave camera has been added to the view + * which it should hook into so that it renders to the appropriate + * texture and submits for XR display. + */ + virtual void addSlave(osg::Camera *slaveCamera) = 0; + + /** + * Notify osgXR that a slave camera is being removed from the view. + * This tells osgXR when a slave camera previously notified with + * addSlave() is being removed. + */ + virtual void removeSlave(osg::Camera *slaveCamera) = 0; + + /* + * Accessors. + */ + + /// Get the OSG GraphicsWindow associated with this osgXR view. + inline const osgViewer::GraphicsWindow *getWindow() const + { + return _window; + } + + /// Get the OSG View associated with this osgXR view. + inline const osgViewer::View *getView() const + { + return _osgView; + } + + protected: + + /* + * Internal + */ + + View(osgViewer::GraphicsWindow *window, osgViewer::View *osgView); + virtual ~View(); + + osg::ref_ptr<osgViewer::GraphicsWindow> _window; + osg::ref_ptr<osgViewer::View> _osgView; + +}; + +} + +#endif diff --git a/3rdparty/osgXR/include/osgXR/osgXR b/3rdparty/osgXR/include/osgXR/osgXR new file mode 100644 index 000000000..8baa33db3 --- /dev/null +++ b/3rdparty/osgXR/include/osgXR/osgXR @@ -0,0 +1,22 @@ +// -*-c++-*- +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_osgXR +#define OSGXR_osgXR 1 + +#include <osgXR/Export> + +#include <osgViewer/Viewer> + +#include <string> + +namespace osgXR { + +void OSGXR_EXPORT setupViewerDefaults(osgViewer::Viewer *viewer, + const std::string &appName, + uint32_t appVersion); + +} + +#endif diff --git a/3rdparty/osgXR/osgXR.pc.in b/3rdparty/osgXR/osgXR.pc.in new file mode 100644 index 000000000..59105e39d --- /dev/null +++ b/3rdparty/osgXR/osgXR.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: @PROJECT_NAME@ +Description: @PROJECT_DESCRIPTION@ +Version: @PROJECT_VERSION@ + +Requires: openscenegraph OpenXR +Libs: -L${libdir} -lmylib +Cflags: -I${includedir} diff --git a/3rdparty/osgXR/src/Action.cpp b/3rdparty/osgXR/src/Action.cpp new file mode 100644 index 000000000..4ccaf537c --- /dev/null +++ b/3rdparty/osgXR/src/Action.cpp @@ -0,0 +1,507 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "Action.h" +#include "ActionSet.h" + +#include "OpenXR/Action.h" +#include "OpenXR/Session.h" +#include "OpenXR/Space.h" + +#include <map> + +using namespace osgXR; + +// Internal API + +Action::Private::Private(ActionSet *actionSet) : + _actionSet(actionSet), + _updated(true) +{ + ActionSet::Private::get(_actionSet)->registerAction(this); +} + +Action::Private::~Private() +{ + ActionSet::Private::get(_actionSet)->unregisterAction(this); +} + +void Action::Private::setName(const std::string &name) +{ + _updated = true; + _name = name; +} + +const std::string &Action::Private::getName() const +{ + return _name; +} + +void Action::Private::setLocalizedName(const std::string &localizedName) +{ + _updated = true; + _localizedName = localizedName; +} + +const std::string &Action::Private::getLocalizedName() const +{ + return _localizedName; +} + +void Action::Private::addSubaction(std::shared_ptr<Subaction::Private> subaction) +{ + _updated = true; + _subactions.insert(subaction); +} + +void Action::Private::cleanupInstance() +{ + _updated = true; + _action = nullptr; +} + +void Action::Private::getBoundSources(std::vector<std::string> &sourcePaths) const +{ + OpenXR::Session *session = ActionSet::Private::get(_actionSet)->getSession(); + if (_action.valid() && session) + { + std::vector<XrPath> paths; + if (session->getActionBoundSources(_action, paths)) + { + // Convert XrPath's into std::string's + OpenXR::Instance *instance = session->getInstance(); + sourcePaths.resize(paths.size()); + for (unsigned int i = 0; i < paths.size(); ++i) + sourcePaths[i] = OpenXR::Path(instance, paths[i]).toString(); + + // Success! + return; + } + } + + // Failure, clear output + sourcePaths.resize(0); +} + +void Action::Private::getBoundSourcesLocalizedNames(XrInputSourceLocalizedNameFlags whichComponents, + std::vector<std::string> &names) const +{ + OpenXR::Session *session = ActionSet::Private::get(_actionSet)->getSession(); + if (_action.valid() && session) + { + std::vector<XrPath> paths; + if (session->getActionBoundSources(_action, paths)) + { + // Convert XrPath's into localized names + names.resize(paths.size()); + for (unsigned int i = 0; i < paths.size(); ++i) + names[i] = session->getInputSourceLocalizedName(paths[i], + whichComponents); + + // Success! + return; + } + } + + // Failure, clear output + names.resize(0); +} + +namespace osgXR { + +template <typename T> +class ActionPrivateCommon : public Action::Private +{ + public: + + typedef typename T::State State; + + ActionPrivateCommon(ActionSet *actionSet) : + Private(actionSet) + { + } + + void cleanupSession() override + { + _states.clear(); + } + + OpenXR::Action *setup(OpenXR::Instance *instance) override + { + OpenXR::ActionSet *actionSet = ActionSet::Private::get(_actionSet)->setup(instance); + if (!actionSet) + { + // Can't continue without an action set + _action = nullptr; + _updated = true; + } + else if (_updated || actionSet != _action->getActionSet()) + { + _action = new T(actionSet, _name, _localizedName); + for (auto &subaction: _subactions) + _action->addSubaction(subaction->setup(instance)); + _updated = false; + } + return _action; + } + + State *getState(Subaction::Private *subaction = nullptr) + { + auto it = _states.find(subaction); + if (it != _states.end()) + return (*it).second.get(); + + OpenXR::Session *session = ActionSet::Private::get(_actionSet)->getSession(); + if (session) + { + OpenXR::Path subactionPath; + if (subaction) + subactionPath = subaction->setup(session->getInstance()); + OpenXR::Action *action = setup(session->getInstance()); + if (action) + { + osg::ref_ptr<State> ret = static_cast<T*>(_action.get())->createState(session, + subactionPath); + _states[subaction] = ret; + return ret.get(); + } + } + return nullptr; + } + + protected: + + std::map<Subaction::Private *, osg::ref_ptr<State>> _states; +}; + +template <typename T> +class ActionPrivateSimple : public ActionPrivateCommon<T> +{ + public: + + typedef typename T::State State; + + ActionPrivateSimple(ActionSet *actionSet) : + ActionPrivateCommon<T>(actionSet) + { + } + + auto getValue(Subaction::Private *subaction) + { + State *state = this->getState(subaction); + if (state && state->update() && state->isActive()) + return state->getCurrentState(); + else + return T::State::Info::defaultValue(); + } +}; + +typedef ActionPrivateSimple<OpenXR::ActionBoolean> ActionPrivateBoolean; +typedef ActionPrivateSimple<OpenXR::ActionFloat> ActionPrivateFloat; +typedef ActionPrivateSimple<OpenXR::ActionVector2f> ActionPrivateVector2f; + +class ActionPrivatePose : public ActionPrivateCommon<OpenXR::ActionPose> +{ + public: + + typedef OpenXR::ActionPose::State State; + + ActionPrivatePose(ActionSet *actionSet) : + ActionPrivateCommon(actionSet) + { + } + + OpenXR::Space *getSpace(Subaction::Private *subaction) + { + State *state = getState(subaction); + if (state && state->update() && state->isActive()) + return state->getSpace(); + else + return nullptr; + } + + bool locate(Subaction::Private *subaction, + ActionPose::Location &location) + { + OpenXR::Space *space = getSpace(subaction); + OpenXR::Session *session = ActionSet::Private::get(_actionSet)->getSession(); + if (session && space) + { + OpenXR::Space::Location loc; + bool ret = space->locate(session->getLocalSpace(), session->getLastDisplayTime(), + loc); + location = ActionPose::Location((ActionPose::Location::Flags)loc.getFlags(), + loc.getOrientation(), + loc.getPosition()); + return ret; + } + else + { + location = ActionPose::Location(); + return false; + } + } +}; + +class ActionPrivateVibration : public ActionPrivateCommon<OpenXR::ActionVibration> +{ + public: + + typedef OpenXR::ActionVibration::State State; + + ActionPrivateVibration(ActionSet *actionSet) : + ActionPrivateCommon(actionSet) + { + } + + bool applyHapticFeedback(Subaction::Private *subaction, + int64_t duration_ns, float frequency, + float amplitude) + { + State *state = getState(subaction); + if (!state) + return false; + return state->applyHapticFeedback(duration_ns, frequency, + amplitude); + } + + bool stopHapticFeedback(Subaction::Private *subaction) + { + State *state = getState(subaction); + if (!state) + return false; + return state->stopHapticFeedback(); + } +}; + +} + +// Public API + +Action::Action(Private *priv) : + _private(priv) +{ +} + +Action::~Action() +{ +} + +void Action::addSubaction(Subaction *subaction) +{ + _private->addSubaction(Subaction::Private::get(subaction)); +} + +void Action::setName(const std::string &name, + const std::string &localizedName) +{ + _private->setName(name); + _private->setLocalizedName(localizedName); +} + +void Action::setName(const std::string &name) +{ + _private->setName(name); +} + +const std::string &Action::getName() const +{ + return _private->getName(); +} + +void Action::setLocalizedName(const std::string &localizedName) +{ + _private->setLocalizedName(localizedName); +} + +const std::string &Action::getLocalizedName() const +{ + return _private->getLocalizedName(); +} + +void Action::getBoundSources(std::vector<std::string> &sourcePaths) const +{ + _private->getBoundSources(sourcePaths); +} + +void Action::getBoundSourcesLocalizedNames(uint32_t whichComponents, + std::vector<std::string> &names) const +{ + _private->getBoundSourcesLocalizedNames(whichComponents, names); +} + +// ActionBoolean + +ActionBoolean::ActionBoolean(ActionSet *actionSet) : + Action(new ActionPrivateBoolean(actionSet)) +{ +} + +ActionBoolean::ActionBoolean(ActionSet *actionSet, + const std::string &name) : + Action(new ActionPrivateBoolean(actionSet)) +{ + setName(name, name); +} + +ActionBoolean::ActionBoolean(ActionSet *actionSet, + const std::string &name, + const std::string &localizedName) : + Action(new ActionPrivateBoolean(actionSet)) +{ + setName(name, localizedName); +} + +bool ActionBoolean::getValue(Subaction *subaction) +{ + auto privSubaction = Subaction::Private::get(subaction); + return static_cast<ActionPrivateBoolean *>(Private::get(this))->getValue(privSubaction.get()); +} + +// ActionFloat + +ActionFloat::ActionFloat(ActionSet *actionSet) : + Action(new ActionPrivateFloat(actionSet)) +{ +} + +ActionFloat::ActionFloat(ActionSet *actionSet, + const std::string &name) : + Action(new ActionPrivateFloat(actionSet)) +{ + setName(name, name); +} + +ActionFloat::ActionFloat(ActionSet *actionSet, + const std::string &name, + const std::string &localizedName) : + Action(new ActionPrivateFloat(actionSet)) +{ + setName(name, localizedName); +} + +float ActionFloat::getValue(Subaction *subaction) +{ + auto privSubaction = Subaction::Private::get(subaction); + return static_cast<ActionPrivateFloat *>(Private::get(this))->getValue(privSubaction.get()); +} + +// ActionVector2f + +ActionVector2f::ActionVector2f(ActionSet *actionSet) : + Action(new ActionPrivateVector2f(actionSet)) +{ +} + +ActionVector2f::ActionVector2f(ActionSet *actionSet, + const std::string &name) : + Action(new ActionPrivateVector2f(actionSet)) +{ + setName(name, name); +} + +ActionVector2f::ActionVector2f(ActionSet *actionSet, + const std::string &name, + const std::string &localizedName) : + Action(new ActionPrivateVector2f(actionSet)) +{ + setName(name, localizedName); +} + +osg::Vec2f ActionVector2f::getValue(Subaction *subaction) +{ + auto privSubaction = Subaction::Private::get(subaction); + return static_cast<ActionPrivateVector2f *>(Private::get(this))->getValue(privSubaction.get()); +} + +// ActionPose + +ActionPose::ActionPose(ActionSet *actionSet) : + Action(new ActionPrivatePose(actionSet)) +{ +} + +ActionPose::ActionPose(ActionSet *actionSet, + const std::string &name) : + Action(new ActionPrivatePose(actionSet)) +{ + setName(name, name); +} + +ActionPose::ActionPose(ActionSet *actionSet, + const std::string &name, + const std::string &localizedName) : + Action(new ActionPrivatePose(actionSet)) +{ + setName(name, localizedName); +} + +ActionPose::Location ActionPose::getValue(Subaction *subaction) +{ + Location location; + auto privSubaction = Subaction::Private::get(subaction); + static_cast<ActionPrivatePose *>(Private::get(this))->locate(privSubaction.get(), + location); + return location; +} + +ActionPose::Location::Location() : + _flags((Flags)0) +{ +} + +ActionPose::Location::Location(Flags flags, + const osg::Quat &orientation, + const osg::Vec3f &position) : + _flags(flags), + _orientation(orientation), + _position(position) +{ +} + +// ActionVibration + +ActionVibration::ActionVibration(ActionSet *actionSet) : + Action(new ActionPrivateVibration(actionSet)) +{ +} + +ActionVibration::ActionVibration(ActionSet *actionSet, + const std::string &name) : + Action(new ActionPrivateVibration(actionSet)) +{ + setName(name, name); +} + +ActionVibration::ActionVibration(ActionSet *actionSet, + const std::string &name, + const std::string &localizedName) : + Action(new ActionPrivateVibration(actionSet)) +{ + setName(name, localizedName); +} + +bool ActionVibration::applyHapticFeedback(int64_t duration_ns, float frequency, + float amplitude) +{ + auto priv = static_cast<ActionPrivateVibration *>(Private::get(this)); + return priv->applyHapticFeedback(nullptr, duration_ns, frequency, + amplitude); +} + +bool ActionVibration::applyHapticFeedback(Subaction *subaction, + int64_t duration_ns, float frequency, + float amplitude) +{ + auto privSubaction = Subaction::Private::get(subaction); + auto priv = static_cast<ActionPrivateVibration *>(Private::get(this)); + return priv->applyHapticFeedback(privSubaction.get(), duration_ns, frequency, + amplitude); +} + +bool ActionVibration::stopHapticFeedback(Subaction *subaction) +{ + auto privSubaction = Subaction::Private::get(subaction); + auto priv = static_cast<ActionPrivateVibration *>(Private::get(this)); + return priv->stopHapticFeedback(privSubaction.get()); +} diff --git a/3rdparty/osgXR/src/Action.h b/3rdparty/osgXR/src/Action.h new file mode 100644 index 000000000..1da217938 --- /dev/null +++ b/3rdparty/osgXR/src/Action.h @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_ACTION +#define OSGXR_ACTION 1 + +#include <osgXR/Action> + +#include "Subaction.h" + +#include <osg/ref_ptr> + +#include <memory> +#include <set> +#include <string> + +namespace osgXR { + +namespace OpenXR { + class Action; + class Instance; +}; + +class Action::Private +{ + public: + + static Private *get(Action *pub) + { + return pub->_private.get(); + } + + Private(ActionSet *actionSet); + virtual ~Private(); + + void setName(const std::string &name); + const std::string &getName() const; + + void setLocalizedName(const std::string &localizedName); + const std::string &getLocalizedName() const; + + void addSubaction(std::shared_ptr<Subaction::Private> subaction); + + bool getUpdated() const + { + return _updated; + } + + /// Setup action with an OpenXR instance + virtual OpenXR::Action *setup(OpenXR::Instance *instance) = 0; + /// Clean up action before an OpenXR session is destroyed + virtual void cleanupSession() = 0; + /// Clean up action before an OpenXR instance is destroyed + void cleanupInstance(); + + /** + * Get a list of currently bound source paths for this action. + * @param sourcePaths[out] Vector of source paths to write into. + */ + void getBoundSources(std::vector<std::string> &sourcePaths) const; + + /** + * Get a list of currently bound source localized names for this action. + * @param whichComponents Which components to include. + * @param names[out] Vector of names to write into. + */ + void getBoundSourcesLocalizedNames(XrInputSourceLocalizedNameFlags whichComponents, + std::vector<std::string> &names) const; + + protected: + + std::string _name; + std::string _localizedName; + + osg::ref_ptr<ActionSet> _actionSet; + std::set<std::shared_ptr<Subaction::Private>> _subactions; + + bool _updated; + osg::ref_ptr<OpenXR::Action> _action; +}; + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/ActionSet.cpp b/3rdparty/osgXR/src/ActionSet.cpp new file mode 100644 index 000000000..56a982a1a --- /dev/null +++ b/3rdparty/osgXR/src/ActionSet.cpp @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "ActionSet.h" +#include "Action.h" + +#include "OpenXR/ActionSet.h" + +#include <osgXR/Manager> + +#include "XRState.h" + +using namespace osgXR; + +// Internal API + +ActionSet::Private::Private(XRState *state) : + _state(state), + _priority(0), + _updated(true) +{ + state->addActionSet(this); +} + +ActionSet::Private::~Private() +{ + XRState *state = _state.get(); + if (state) + state->removeActionSet(this); +} + +void ActionSet::Private::setName(const std::string &name) +{ + _updated = true; + _name = name; +} + +const std::string &ActionSet::Private::getName() const +{ + return _name; +} + +void ActionSet::Private::setLocalizedName(const std::string &localizedName) +{ + _updated = true; + _localizedName = localizedName; +} + +const std::string &ActionSet::Private::getLocalizedName() const +{ + return _localizedName; +} + +void ActionSet::Private::setPriority(uint32_t priority) +{ + _updated = true; + _priority = priority; +} + +uint32_t ActionSet::Private::getPriority() const +{ + return _priority; +} + +bool ActionSet::Private::getUpdated() const +{ + if (_updated) + return true; + for (Action::Private *action: _actions) + if (action->getUpdated()) + return true; + return false; +} + +void ActionSet::Private::activate(std::shared_ptr<Subaction::Private> subaction) +{ + _activeSubactions.insert(subaction); + + if (_actionSet.valid() && _session.valid()) + { + OpenXR::Path path; + if (subaction) + path = subaction->setup(_session->getInstance()); + _session->activateActionSet(_actionSet, path); + } +} + +void ActionSet::Private::deactivate(std::shared_ptr<Subaction::Private> subaction) +{ + _activeSubactions.erase(subaction); + + if (_actionSet.valid() && _session.valid()) + { + OpenXR::Path path; + if (subaction) + path = subaction->setup(_session->getInstance()); + _session->deactivateActionSet(_actionSet, path); + } +} + +bool ActionSet::Private::isActive() +{ + return !_activeSubactions.empty(); +} + +void ActionSet::Private::registerAction(Action::Private *action) +{ + _actions.insert(action); +} + +void ActionSet::Private::unregisterAction(Action::Private *action) +{ + _actions.erase(action); +} + +OpenXR::ActionSet *ActionSet::Private::setup(OpenXR::Instance *instance) +{ + if (_updated) + { + _actionSet = new OpenXR::ActionSet(instance, _name, _localizedName, + _priority); + _updated = false; + } + return _actionSet; +} + +bool ActionSet::Private::setup(OpenXR::Session *session) +{ + _session = session; + if (_actionSet.valid()) + { + session->addActionSet(_actionSet); + // Init all the actions + for (Action::Private *action: _actions) + { + OpenXR::Action *xrAction = action->setup(session->getInstance()); + if (xrAction) + xrAction->init(); + } + for (auto &subaction: _activeSubactions) + { + OpenXR::Path path; + if (subaction) + path = subaction->setup(session->getInstance()); + session->activateActionSet(_actionSet, path); + } + return true; + } + return false; +} + +void ActionSet::Private::cleanupSession() +{ + for (auto *action: _actions) + action->cleanupSession(); +} + +void ActionSet::Private::cleanupInstance() +{ + _updated = true; + _actionSet = nullptr; + for (auto *action: _actions) + action->cleanupInstance(); +} + +// Public API + +ActionSet::ActionSet(Manager *manager) : + _private(new Private(manager->_getXrState())) +{ +} + +ActionSet::ActionSet(Manager *manager, + const std::string &name) : + _private(new Private(manager->_getXrState())) +{ + setName(name, name); +} + +ActionSet::ActionSet(Manager *manager, + const std::string &name, + const std::string &localizedName) : + _private(new Private(manager->_getXrState())) +{ + setName(name, localizedName); +} + +ActionSet::~ActionSet() +{ +} + +void ActionSet::setName(const std::string &name, + const std::string &localizedName) +{ + _private->setName(name); + _private->setLocalizedName(localizedName); +} + +void ActionSet::setName(const std::string &name) +{ + _private->setName(name); +} + +const std::string &ActionSet::getName() const +{ + return _private->getName(); +} + +void ActionSet::setLocalizedName(const std::string &localizedName) +{ + _private->setLocalizedName(localizedName); +} + +const std::string &ActionSet::getLocalizedName() const +{ + return _private->getLocalizedName(); +} + +void ActionSet::setPriority(uint32_t priority) +{ + _private->setPriority(priority); +} + +uint32_t ActionSet::getPriority() const +{ + return _private->getPriority(); +} + +void ActionSet::activate(Subaction *subaction) +{ + _private->activate(Subaction::Private::get(subaction)); +} + +void ActionSet::deactivate(Subaction *subaction) +{ + _private->deactivate(Subaction::Private::get(subaction)); +} + +bool ActionSet::isActive() +{ + return _private->isActive(); +} diff --git a/3rdparty/osgXR/src/ActionSet.h b/3rdparty/osgXR/src/ActionSet.h new file mode 100644 index 000000000..776836323 --- /dev/null +++ b/3rdparty/osgXR/src/ActionSet.h @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_ACTION_SET +#define OSGXR_ACTION_SET 1 + +#include <osgXR/ActionSet> +#include <osgXR/Action> + +#include "OpenXR/Path.h" + +#include "Subaction.h" + +#include <osg/observer_ptr> +#include <osg/ref_ptr> + +#include <memory> +#include <string> +#include <set> + +namespace osgXR { + +class Action; +class XRState; + +namespace OpenXR { + class ActionSet; + class Instance; + class Session; +}; + +class ActionSet::Private +{ + public: + + static Private *get(ActionSet *pub) + { + return pub->_private.get(); + } + + Private(XRState *state); + ~Private(); + + void setName(const std::string &name); + const std::string &getName() const; + + void setLocalizedName(const std::string &localizedName); + const std::string &getLocalizedName() const; + + void setPriority(uint32_t priority); + uint32_t getPriority() const; + + bool getUpdated() const; + + void activate(std::shared_ptr<Subaction::Private> subaction = nullptr); + void deactivate(std::shared_ptr<Subaction::Private> subaction = nullptr); + bool isActive(); + + void registerAction(Action::Private *action); + void unregisterAction(Action::Private *action); + + /// Setup action set with an OpenXR instance + OpenXR::ActionSet *setup(OpenXR::Instance *instance); + /// Setup action set with an OpenXR session + bool setup(OpenXR::Session *session); + /// Clean up action before an OpenXR session is destroyed + void cleanupSession(); + /// Clean up action before an OpenXR instance is destroyed + void cleanupInstance(); + + OpenXR::Session *getSession() + { + return _session.get(); + } + + protected: + + osg::observer_ptr<XRState> _state; + std::string _name; + std::string _localizedName; + uint32_t _priority; + std::set<std::shared_ptr<Subaction::Private>> _activeSubactions; + + std::set<Action::Private *> _actions; + + bool _updated; + osg::ref_ptr<OpenXR::ActionSet> _actionSet; + osg::observer_ptr<OpenXR::Session> _session; +}; + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/CMakeLists.txt b/3rdparty/osgXR/src/CMakeLists.txt new file mode 100644 index 000000000..f434a7ec7 --- /dev/null +++ b/3rdparty/osgXR/src/CMakeLists.txt @@ -0,0 +1,132 @@ +# Dependencies +find_package(OpenGL REQUIRED) +find_package(OpenSceneGraph REQUIRED COMPONENTS osgViewer osgUtil) +find_package(OpenXR REQUIRED) + +# Public header files +set(osgXR_HEADERS + include/osgXR/Action + include/osgXR/ActionSet + include/osgXR/Export + include/osgXR/InteractionProfile + include/osgXR/Manager + include/osgXR/Mirror + include/osgXR/MirrorSettings + include/osgXR/OpenXRDisplay + include/osgXR/Settings + include/osgXR/Subaction + include/osgXR/View + include/osgXR/osgXR +) + +# Source files +set(osgXR_SRCS + OpenXR/Action.cpp + OpenXR/ActionSet.cpp + OpenXR/Compositor.cpp + OpenXR/EventHandler.cpp + OpenXR/GraphicsBinding.cpp + OpenXR/Instance.cpp + OpenXR/InteractionProfile.cpp + OpenXR/Path.cpp + OpenXR/Session.cpp + OpenXR/Space.cpp + OpenXR/Swapchain.cpp + OpenXR/SwapchainGroup.cpp + OpenXR/System.cpp + XRFramebuffer.cpp + XRState.cpp + XRRealizeOperation.cpp + Action.cpp + ActionSet.cpp + FrameStore.cpp + InteractionProfile.cpp + Manager.cpp + Mirror.cpp + MirrorSettings.cpp + OpenXRDisplay.cpp + Settings.cpp + Subaction.cpp + View.cpp + osgXR.cpp + projection.cpp +) + +# Win32 graphics binding +if(WIN32) + list(APPEND osgXR_SRCS + OpenXR/GraphicsBindingWin32.cpp + ) + add_compile_definitions(OSGXR_USE_WIN32) +endif() + +# X11 graphics binding +find_package(X11) +if(X11_FOUND) + list(APPEND osgXR_SRCS + OpenXR/GraphicsBindingX11.cpp + ) + add_compile_definitions(OSGXR_USE_X11) +endif() + + +# Build osgXR as a library +add_library(osgXR ${osgXR_LIBRARY_TYPE} ${osgXR_SRCS}) + +get_target_property(osgXR_TYPE osgXR TYPE) +if(osgXR_TYPE STREQUAL STATIC_LIBRARY) + # Needed to switch OSGXR_EXPORT off on Windows + set(OSGXR_STATIC_LIBRARY 1) +endif() +# Needed to switch OSGXR_EXPORT to dllexport on Windows +add_compile_definitions(OSGXR_LIBRARY) + +# Generate a "generated/Version.h" header +set(osgXR_VERSION_HEADER "${PROJECT_BINARY_DIR}/include/generated/Version.h") +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Version.h.in" + "${osgXR_VERSION_HEADER}") + +# Generate "osgXR/Config" header +set(osgXR_CONFIG_HEADER "${PROJECT_BINARY_DIR}/include/osgXR/Config") +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Config.in" + "${osgXR_CONFIG_HEADER}") +list(APPEND osgXR_HEADERS "${osgXR_CONFIG_HEADER}") + +# Ensure required C++ standards are available +target_compile_features(osgXR + # smart pointers + PUBLIC cxx_std_11 + # std::optional + PRIVATE cxx_std_17) + +target_include_directories(osgXR + PRIVATE + ${PROJECT_BINARY_DIR}/include + ${PROJECT_SOURCE_DIR}/include + ${OPENGL_INCLUDE_DIR} + ${OPENSCENEGRAPH_INCLUDE_DIRS} + ${OpenXR_INCLUDE_DIR} + PUBLIC + "$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>" + "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>" +) + +target_link_libraries(osgXR + PRIVATE + ${OPENGL_LIBRARIES} + PUBLIC + ${OPENSCENEGRAPH_LIBRARIES} + OpenXR::openxr_loader +) + +set_target_properties(osgXR + PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${osgXR_SOVERSION} + PUBLIC_HEADER "${osgXR_HEADERS}" + INTERFACE_osgXR_MAJOR_VERSION ${osgXR_MAJOR_VERSION} + INTERFACE_osgXR_MINOR_VERSION ${osgXR_MINOR_VERSION} +) +set_property(TARGET osgXR APPEND PROPERTY + COMPATIBLE_INTERFACE_STRING osgXR_MAJOR_VERSION osgXR_MINOR_VERSION +) diff --git a/3rdparty/osgXR/src/Config.in b/3rdparty/osgXR/src/Config.in new file mode 100644 index 000000000..2cdac032c --- /dev/null +++ b/3rdparty/osgXR/src/Config.in @@ -0,0 +1,10 @@ +// -*-c++-*- +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_Config +#define OSGXR_Config 1 + +#cmakedefine OSGXR_STATIC_LIBRARY + +#endif diff --git a/3rdparty/osgXR/src/FrameStampedVector.h b/3rdparty/osgXR/src/FrameStampedVector.h new file mode 100644 index 000000000..715d788a1 --- /dev/null +++ b/3rdparty/osgXR/src/FrameStampedVector.h @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_FRAME_STAMPED_VECTOR +#define OSGXR_FRAME_STAMPED_VECTOR 1 + +#include <osg/FrameStamp> + +#include <optional> +#include <utility> +#include <vector> + +namespace osgXR { + +/** + * Manages frame stamping of vector items. + * Contains a vector of the chosen type, with each item stamped with a + * FrameStamp. Items can be retrieved by index or FrameStamp. + */ +template <typename T> +class FrameStampedVector +{ + public: + + typedef T Item; + typedef unsigned int FrameNumber; + typedef const osg::FrameStamp *Stamp; + typedef std::pair<Item, FrameNumber> StampedItem; + + void reserve(unsigned int len) + { + _vec.reserve(len); + } + + void resize(unsigned int len, Item item = Item()) + { + _vec.resize(len, StampedItem(item, ~0)); + } + + unsigned int size() const + { + return _vec.size(); + } + + void push_back(const Item &item) + { + _vec.push_back(StampedItem(item, ~0)); + } + + // operator [] provides an Item if indexed directly + const Item &operator [] (unsigned int index) const + { + return _vec[index].first; + } + + // operator [] provides an optional Item if indexed by stamp + std::optional<const Item> operator [] (Stamp stamp) const + { + int index = findStamp(stamp); + if (index < 0) + return std::nullopt; + return _vec[index].first; + } + + int findStamp(Stamp stamp) const + { + unsigned int frameNumber = stamp->getFrameNumber(); + for (unsigned int i = 0; i < _vec.size(); ++i) + if (_vec[i].second == frameNumber) + return i; + return -1; + } + + void setStamp(unsigned int index, Stamp stamp) + { + _vec[index].second = stamp->getFrameNumber(); + } + + protected: + + std::vector<StampedItem> _vec; +}; + +} + +#endif diff --git a/3rdparty/osgXR/src/FrameStore.cpp b/3rdparty/osgXR/src/FrameStore.cpp new file mode 100644 index 000000000..d7276f3fe --- /dev/null +++ b/3rdparty/osgXR/src/FrameStore.cpp @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "FrameStore.h" + +#include <osg/FrameStamp> + +#include <cassert> + +using namespace osgXR; + +FrameStore::FrameStore() +{ +} + +osg::ref_ptr<FrameStore::Frame> FrameStore::getFrame(FrameStore::Stamp stamp) +{ + OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex); + + int index = lookupFrame(stamp); + if (index < 0) + return nullptr; + + return _store[index]; +} + +osg::ref_ptr<FrameStore::Frame> FrameStore::getFrame(FrameStore::Stamp stamp, + OpenXR::Session *session) +{ + OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex); + + int index = lookupFrame(stamp); + if (index < 0) + { + index = blankFrame(); + // there surely shouldn't be more than 2 frames in parallel + assert(index >= 0); + if (index < 0) + return nullptr; + + osg::ref_ptr<OpenXR::Session::Frame> frame = session->waitFrame(); + if (frame.valid()) + { + frame->setOsgFrameNumber(stamp->getFrameNumber()); + _store[index] = frame; + } + return frame; + } + + return _store[index]; +} + +bool FrameStore::endFrame(FrameStore::Stamp stamp) +{ + OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex); + + int index = lookupFrame(stamp); + if (index < 0) + return false; + + _store[index]->end(); + _store[index] = nullptr; + + return true; +} + +int FrameStore::lookupFrame(FrameStore::Stamp stamp) const +{ + unsigned int frameNumber = stamp->getFrameNumber(); + for (unsigned int i = 0; i < maxFrames; ++i) + { + if (_store[i].valid() && + _store[i]->getOsgFrameNumber() == frameNumber) + { + return i; + } + } + return -1; +} + +int FrameStore::blankFrame() const +{ + for (unsigned int i = 0; i < maxFrames; ++i) + if (!_store[i].valid()) + return i; + return -1; +} diff --git a/3rdparty/osgXR/src/FrameStore.h b/3rdparty/osgXR/src/FrameStore.h new file mode 100644 index 000000000..4267cd6ab --- /dev/null +++ b/3rdparty/osgXR/src/FrameStore.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_FRAME_STORE +#define OSGXR_FRAME_STORE 1 + +#include "OpenXR/Session.h" + +#include <osg/ref_ptr> +#include <OpenThreads/Mutex> + +#include <vector> + +namespace osg { + class FrameStamp; +} + +namespace osgXR { + +/** + * Manages concurrent frames. + * A FrameStore stores any concurrent OpenXR frames and allows them to be + * created and retrieved in a thread-safe way based on an osg::FrameStamp. + */ +class FrameStore +{ + public: + + typedef OpenXR::Session::Frame Frame; + typedef const osg::FrameStamp *Stamp; + + FrameStore(); + + /// Get a frame by FrameStamp. + osg::ref_ptr<Frame> getFrame(Stamp stamp); + + /// Get or wait for a frame by FrameStamp. + osg::ref_ptr<Frame> getFrame(Stamp stamp, OpenXR::Session *session); + + /** + * End a frame by FrameStamp. + * @return true on success, false otherwise. + */ + bool endFrame(Stamp stamp); + + protected: + + // These return cache index or -1 + int lookupFrame(Stamp stamp) const; + int blankFrame() const; + + // 2 allows work to start on next frame before the prior one has ended + static constexpr unsigned int maxFrames = 2; + // Protected by _mutex + osg::ref_ptr<Frame> _store[maxFrames]; + + // For access to _store + OpenThreads::Mutex _mutex; +}; + +} + +#endif diff --git a/3rdparty/osgXR/src/InteractionProfile.cpp b/3rdparty/osgXR/src/InteractionProfile.cpp new file mode 100644 index 000000000..a067b2ea8 --- /dev/null +++ b/3rdparty/osgXR/src/InteractionProfile.cpp @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "Action.h" +#include "InteractionProfile.h" + +#include "OpenXR/InteractionProfile.h" +#include "OpenXR/Path.h" +#include "OpenXR/Session.h" + +#include <osgXR/Manager> + +#include "XRState.h" + +using namespace osgXR; + +// Internal API + +InteractionProfile::Private::Private(InteractionProfile *pub, + XRState *state, + const std::string &vendor, + const std::string &type) : + _pub(pub), + _state(state), + _vendor(vendor), + _type(type), + _updated(true) +{ + state->addInteractionProfile(this); +} + +InteractionProfile::Private::~Private() +{ + XRState *state = _state.get(); + if (state) + state->removeInteractionProfile(this); +} + +void InteractionProfile::Private::suggestBinding(Action *action, + const std::string &binding) +{ + _bindings.push_back({action, binding}); + _updated = true; +} + +bool InteractionProfile::Private::setup(OpenXR::Instance *instance) +{ + // Recreate every time, as actions may have been altered and recreated + _profile = new OpenXR::InteractionProfile(instance, _vendor.c_str(), + _type.c_str()); + + for (Binding &binding: _bindings) + { + // ensure action is set up + OpenXR::Action *action = Action::Private::get(binding.action)->setup(instance); + if (action) + _profile->addBinding(action, binding.binding); + } + + bool ret = _profile->suggestBindings(); + if (ret) + _updated = false; + return ret; +} + +void InteractionProfile::Private::cleanupInstance() +{ + _profile = nullptr; +} + +OpenXR::Path InteractionProfile::Private::getPath() const +{ + if (_profile.valid()) + return _profile->getPath(); + else + return OpenXR::Path(); +} + +// Public API + +InteractionProfile::InteractionProfile(Manager *manager, + const std::string &vendor, + const std::string &type) : + _private(new Private(this, manager->_getXrState(), vendor, type)) +{ +} + +InteractionProfile::~InteractionProfile() +{ +} + +const std::string &InteractionProfile::getVendor() const +{ + return _private->getVendor(); +} + +const std::string &InteractionProfile::getType() const +{ + return _private->getType(); +} + +void InteractionProfile::suggestBinding(Action *action, + const std::string &binding) +{ + _private->suggestBinding(action, binding); +} diff --git a/3rdparty/osgXR/src/InteractionProfile.h b/3rdparty/osgXR/src/InteractionProfile.h new file mode 100644 index 000000000..59485de65 --- /dev/null +++ b/3rdparty/osgXR/src/InteractionProfile.h @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_INTERACTION_PROFILE +#define OSGXR_INTERACTION_PROFILE 1 + +#include <osgXR/InteractionProfile> + +#include <osg/observer_ptr> +#include <osg/ref_ptr> + +#include <list> +#include <string> + +namespace osgXR { + +class XRState; + +namespace OpenXR { + class InteractionProfile; + class Path; + class Session; +}; + +class InteractionProfile::Private +{ + public: + + static Private *get(InteractionProfile *pub) + { + return pub->_private.get(); + } + + Private(InteractionProfile *pub, + XRState *newState, + const std::string &newVendor, + const std::string &newType); + ~Private(); + + void suggestBinding(Action *action, const std::string &binding); + + bool getUpdated() const + { + return _updated; + } + + /// Setup bindings with an OpenXR instance + bool setup(OpenXR::Instance *instance); + /// Clean up bindings before an OpenXR instance is destroyed + void cleanupInstance(); + + // Accessors + + /// Get the public object. + InteractionProfile *getPublic() + { + return _pub; + } + + /// Get the vendor segment of the OpenXR interaction profile path. + const std::string &getVendor() const + { + return _vendor; + } + + /// Get the type segment of the OpenXR interaction profile path. + const std::string &getType() const + { + return _type; + } + + OpenXR::Path getPath() const; + + private: + + InteractionProfile *_pub; + osg::observer_ptr<XRState> _state; + std::string _vendor; + std::string _type; + + struct Binding { + osg::ref_ptr<Action> action; + std::string binding; + }; + std::list<Binding> _bindings; + + bool _updated; + osg::ref_ptr<OpenXR::InteractionProfile> _profile; +}; + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/Manager.cpp b/3rdparty/osgXR/src/Manager.cpp new file mode 100644 index 000000000..39ce0d764 --- /dev/null +++ b/3rdparty/osgXR/src/Manager.cpp @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include <osgXR/Manager> +#include <osgXR/Mirror> + +#include "XRState.h" +#include "XRRealizeOperation.h" + +using namespace osgXR; + +Manager::Manager() : + _settings(Settings::instance()), + _destroying(false), + _state(new XRState(_settings, const_cast<Manager *>(this))) +{ +} + +Manager::~Manager() +{ +} + +void Manager::setVisibilityMaskNodeMasks(osg::Node::NodeMask left, + osg::Node::NodeMask right) const +{ + _state->setVisibilityMaskNodeMasks(left, right); +} + +void Manager::configure(osgViewer::View &view) const +{ + osgViewer::ViewerBase *viewer = _viewer; + if (!viewer) + viewer = dynamic_cast<osgViewer::ViewerBase *>(&view); + if (!viewer) + return; + + _state->setViewer(viewer); + + // Its rather inconvenient that ViewConfig expects a const configure() + // Just cheat and cast away the constness here + osg::ref_ptr<XRRealizeOperation> realizeOp = new XRRealizeOperation(_state, &view); + viewer->setRealizeOperation(realizeOp); + if (viewer->isRealized()) + { + osgViewer::ViewerBase::Contexts contexts; + viewer->getContexts(contexts, true); + if (contexts.size() > 0) + (*realizeOp)(contexts[0]); + } +} + +void Manager::update() +{ + _state->update(); +} + +bool Manager::checkAndResetStateChanged() +{ + return _state->checkAndResetStateChanged(); +} + +bool Manager::getPresent() const +{ + return _state->getUpState() >= XRState::VRSTATE_SYSTEM; +} + +bool Manager::getEnabled() const +{ + return _state->getUpState() == XRState::VRSTATE_ACTIONS; +} + +void Manager::setEnabled(bool enabled) +{ + // Avoid needlessly discarding of the instance + // SteamVR 1.15 and 1.16 have issues with xrDestroySession() hanging + if (enabled) + { + _destroying = false; + _state->setProbing(true); + } + else if (_destroying) + { + _state->setProbing(false); + } + + _state->setDestState(enabled ? XRState::VRSTATE_ACTIONS + : _state->getProbingState()); +} + +void Manager::destroyAndWait() +{ + _destroying = true; + setEnabled(false); + while (_state->isStateUpdateNeeded()) + _state->update(); +} + +bool Manager::isDestroying() const +{ + return _destroying; +} + +bool Manager::isRunning() const +{ + return _state->isRunning(); +} + +void Manager::syncSettings() +{ + _state->syncSettings(); +} + +void Manager::syncActionSetup() +{ + _state->syncActionSetup(); +} + +bool Manager::hasValidationLayer() const +{ + return _state->hasValidationLayer(); +} + +bool Manager::hasDepthInfoExtension() const +{ + return _state->hasDepthInfoExtension(); +} + +bool Manager::hasVisibilityMaskExtension() const +{ + return _state->hasVisibilityMaskExtension(); +} + +const char *Manager::getRuntimeName() const +{ + return _state->getRuntimeName(); +} + +const char *Manager::getSystemName() const +{ + return _state->getSystemName(); +} + +const char *Manager::getStateString() const +{ + return _state->getStateString(); +} + +void Manager::onRunning() +{ +} + +void Manager::onStopped() +{ +} + +void Manager::onFocus() +{ +} + +void Manager::onUnfocus() +{ +} + +void Manager::addMirror(Mirror *mirror) +{ + if (!_state->valid()) + { + // handle this later, _state may not be created yet + _mirrorQueue.push_back(mirror); + } + else + { + // init the mirror right away + mirror->_init(); + } +} + +void Manager::setupMirrorCamera(osg::Camera *camera) +{ + addMirror(new Mirror(this, camera)); +} + +void Manager::_setupMirrors() +{ + // init each mirror in the queue + while (!_mirrorQueue.empty()) + { + _mirrorQueue.front()->_init(); + _mirrorQueue.pop_front(); + } +} diff --git a/3rdparty/osgXR/src/Mirror.cpp b/3rdparty/osgXR/src/Mirror.cpp new file mode 100644 index 000000000..a7249dfe0 --- /dev/null +++ b/3rdparty/osgXR/src/Mirror.cpp @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include <osgXR/Manager> +#include <osgXR/Mirror> + +#include "XRState.h" + +#include <osg/PolygonMode> + +using namespace osgXR; + +Mirror::Mirror(Manager *manager, osg::Camera *camera) : + _manager(manager), + _camera(camera), + _mirrorSettings(manager->_getSettings()->getMirrorSettings()) +{ +} + +Mirror::~Mirror() +{ +} + +void Mirror::_init() +{ + _camera->setAllowEventFocus(false); + _camera->setViewMatrix(osg::Matrix::identity()); + _camera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1)); + + // Find the mirror settings + MirrorSettings *mirrorSettings = &_mirrorSettings; + // but fall back to the manager's mirror settings + if (mirrorSettings->getMirrorMode() == MirrorSettings::MIRROR_AUTOMATIC) + mirrorSettings = &_manager->_getSettings()->getMirrorSettings(); + switch (mirrorSettings->getMirrorMode()) + { + case MirrorSettings::MIRROR_NONE: + // Draw nothing, but still clear the viewport + _camera->setClearMask(GL_COLOR_BUFFER_BIT); + break; + case MirrorSettings::MIRROR_AUTOMATIC: + // Fall-through: Default to MIRROR_SINGLE + case MirrorSettings::MIRROR_SINGLE: + { + int viewIndex = mirrorSettings->getMirrorViewIndex(); + if (viewIndex < 0) + viewIndex = 0; + setupQuad(viewIndex, 0.0f, 1.0f); + } + break; + case MirrorSettings::MIRROR_LEFT_RIGHT: + for (unsigned int viewIndex = 0; viewIndex < 2; ++viewIndex) + setupQuad(viewIndex, 0.5f * viewIndex, 0.5f); + break; + } +} + +namespace { + +class MirrorPreDrawCallback : public osg::Camera::DrawCallback +{ + public: + + MirrorPreDrawCallback(osg::ref_ptr<XRState> xrState, + osg::ref_ptr<osg::StateSet> stateSet, + unsigned int viewIndex) : + _xrState(xrState), + _stateSet(stateSet), + _viewIndex(viewIndex) + { + } + + void operator()(osg::RenderInfo& renderInfo) const override + { + const osg::FrameStamp *stamp = renderInfo.getState()->getFrameStamp(); + _stateSet->setTextureAttributeAndModes(0, + _xrState->getViewTexture(_viewIndex, stamp)); + } + + protected: + + osg::observer_ptr<XRState> _xrState; + osg::ref_ptr<osg::StateSet> _stateSet; + unsigned int _viewIndex; +}; + +class MirrorPostDrawCallback : public osg::Camera::DrawCallback +{ + public: + + MirrorPostDrawCallback(osg::ref_ptr<osg::StateSet> stateSet) : + _stateSet(stateSet) + { + } + + void operator()(osg::RenderInfo& renderInfo) const override + { + _stateSet->removeTextureAttribute(0, osg::StateAttribute::Type::TEXTURE); + } + + protected: + + osg::ref_ptr<osg::StateSet> _stateSet; +}; + +} + +void Mirror::setupQuad(unsigned int viewIndex, + float x, float w) +{ + XRState *xrState = _manager->_getXrState(); + + if (viewIndex >= xrState->getViewCount()) + return; + + // Build an always-visible quad to draw the view texture on + osg::ref_ptr<osg::Geode> quad = new osg::Geode; + quad->setCullingActive(false); + + XRState::TextureRect rect = xrState->getViewTextureRect(viewIndex); + quad->addDrawable(osg::createTexturedQuadGeometry( + osg::Vec3(x, 0.0f, 0.0f), + osg::Vec3(w, 0.0f, 0.0f), + osg::Vec3(0.0f, 1.0f, 0.0f), + rect.x, rect.y, + rect.x + rect.width, rect.y + rect.height)); + + osg::ref_ptr<osg::StateSet> state = quad->getOrCreateStateSet(); + int forceOff = osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED; + state->setMode(GL_LIGHTING, forceOff); + state->setMode(GL_DEPTH_TEST, forceOff); + + _camera->addChild(quad); + + // Set a callback so we can switch the texture to the active swapchain image + _camera->addPreDrawCallback(new MirrorPreDrawCallback(_manager->_getXrState(), + state, viewIndex)); + _camera->addPostDrawCallback(new MirrorPostDrawCallback(state)); +} diff --git a/3rdparty/osgXR/src/MirrorSettings.cpp b/3rdparty/osgXR/src/MirrorSettings.cpp new file mode 100644 index 000000000..9d718045f --- /dev/null +++ b/3rdparty/osgXR/src/MirrorSettings.cpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include <osgXR/MirrorSettings> + +using namespace osgXR; + +MirrorSettings::MirrorSettings() : + _mirrorMode(MIRROR_AUTOMATIC), + _mirrorViewIndex(-1) +{ +} diff --git a/3rdparty/osgXR/src/OpenXR/Action.cpp b/3rdparty/osgXR/src/OpenXR/Action.cpp new file mode 100644 index 000000000..7fae4a85e --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/Action.cpp @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "Action.h" +#include "Path.h" +#include "Session.h" +#include "Space.h" + +#include <cassert> +#include <cstring> + +using namespace osgXR::OpenXR; + +Action::Action(ActionSet *actionSet, + const std::string &name, + const std::string &localizedName, + XrActionType type) : + _actionSet(actionSet), + _createInfo{ XR_TYPE_ACTION_CREATE_INFO }, + _action(XR_NULL_HANDLE) +{ + strncpy(_createInfo.actionName, name.c_str(), + XR_MAX_ACTION_NAME_SIZE - 1); + strncpy(_createInfo.localizedActionName, localizedName.c_str(), + XR_MAX_LOCALIZED_ACTION_NAME_SIZE - 1); + _createInfo.actionType = type; +} + +Action::~Action() +{ + if (_action != XR_NULL_HANDLE) + { + check(xrDestroyAction(_action), + "Failed to destroy OpenXR action"); + } +} + +void Action::addSubaction(const Path &path) +{ + assert(path.getInstance() == getInstance()); + _subactionPaths.push_back(path.getXrPath()); +} + +bool Action::init() +{ + if (valid()) + return true; + + if (!_subactionPaths.empty()) + { + _createInfo.countSubactionPaths = _subactionPaths.size(); + _createInfo.subactionPaths = _subactionPaths.data(); + } + return check(xrCreateAction(getXrActionSet(), &_createInfo, &_action), + "Failed to create OpenXR action"); +} + +ActionStateBase::ActionStateBase(Action *action, Session *session, + Path subactionPath) : + _action(action), + _session(session), + _subactionPath(subactionPath), + _valid(false), + _syncCount(0) +{ +} + +ActionStateBase::~ActionStateBase() +{ +} + +bool ActionStateBase::checkUpdate() +{ + unsigned int sessionSyncCount = _session->getActionSyncCount(); + // If an xrSyncActions has taken place, the state is out of date + bool needsUpdate = (_syncCount < sessionSyncCount); + // Update the counter as caller is expected to update the state + _syncCount = sessionSyncCount; + return needsUpdate; +} + +template <> +bool ActionStateCommonBoolean::updateState() +{ + XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO }; + getInfo.action = _action->getXrAction(); + getInfo.subactionPath = _subactionPath.getXrPath(); + + _state = { XR_TYPE_ACTION_STATE_BOOLEAN }; + + _valid = check(xrGetActionStateBoolean(_session->getXrSession(), &getInfo, + &_state), + "Failed to get boolean OpenXR action state"); + return _valid; +} + +template <> +bool ActionStateCommonFloat::updateState() +{ + XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO }; + getInfo.action = _action->getXrAction(); + getInfo.subactionPath = _subactionPath.getXrPath(); + + _state = { XR_TYPE_ACTION_STATE_FLOAT }; + + _valid = check(xrGetActionStateFloat(_session->getXrSession(), &getInfo, + &_state), + "Failed to get float OpenXR action state"); + return _valid; +} + +template <> +bool ActionStateCommonVector2f::updateState() +{ + XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO }; + getInfo.action = _action->getXrAction(); + getInfo.subactionPath = _subactionPath.getXrPath(); + + _state = { XR_TYPE_ACTION_STATE_VECTOR2F }; + + _valid = check(xrGetActionStateVector2f(_session->getXrSession(), &getInfo, + &_state), + "Failed to get vector2f OpenXR action state"); + return _valid; +} + +template <> +bool ActionStateCommonPose::updateState() +{ + XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO }; + getInfo.action = _action->getXrAction(); + getInfo.subactionPath = _subactionPath.getXrPath(); + + _state = { XR_TYPE_ACTION_STATE_POSE }; + + _valid = check(xrGetActionStatePose(_session->getXrSession(), &getInfo, + &_state), + "Failed to get pose OpenXR action state"); + return _valid; +} + +ActionStatePose::ActionStatePose(ActionPose *action, Session *session, + Path subactionPath) : + Base(action, session, subactionPath), + _space(new Space(session, action, subactionPath)) +{ +} + +ActionStatePose::~ActionStatePose() +{ +} + +ActionStateVibration::ActionStateVibration(ActionVibration *action, + Session *session, + Path subactionPath) : + _action(action), + _session(session), + _subactionPath(subactionPath) +{ +} + +bool ActionStateVibration::applyHapticFeedback(int64_t duration_ns, + float frequency, + float amplitude) const +{ + XrHapticActionInfo actionInfo{ XR_TYPE_HAPTIC_ACTION_INFO }; + actionInfo.action = _action->getXrAction(); + actionInfo.subactionPath = _subactionPath.getXrPath(); + + XrHapticVibration vibration{ XR_TYPE_HAPTIC_VIBRATION }; + vibration.duration = duration_ns; + vibration.frequency = frequency; + vibration.amplitude = amplitude; + + return check(xrApplyHapticFeedback(_session->getXrSession(), &actionInfo, + reinterpret_cast<XrHapticBaseHeader*>(&vibration)), + "Failed to apply haptic feedback"); +} + +bool ActionStateVibration::stopHapticFeedback() const +{ + XrHapticActionInfo actionInfo{ XR_TYPE_HAPTIC_ACTION_INFO }; + actionInfo.action = _action->getXrAction(); + actionInfo.subactionPath = _subactionPath.getXrPath(); + + return check(xrStopHapticFeedback(_session->getXrSession(), &actionInfo), + "Failed to stop haptic feedback"); +} diff --git a/3rdparty/osgXR/src/OpenXR/Action.h b/3rdparty/osgXR/src/OpenXR/Action.h new file mode 100644 index 000000000..2bbf71462 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/Action.h @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_ACTION +#define OSGXR_OPENXR_ACTION 1 + +#include "ActionSet.h" +#include "Path.h" + +#include <osg/Vec2f> +#include <osg/ref_ptr> + +#include <cassert> +#include <string> +#include <vector> + +namespace osgXR { + +namespace OpenXR { + +class Path; +class Space; + +class Action : public osg::Referenced +{ + public: + + Action(ActionSet *actionSet, + const std::string &name, + const std::string &localizedName, + XrActionType type); + virtual ~Action(); + + // Action initialisation + + void addSubaction(const Path &path); + + // Returns true on success + bool init(); + + // Error checking + + inline bool valid() const + { + return _action != XR_NULL_HANDLE; + } + + inline bool check(XrResult result, const char *warnMsg) const + { + return _actionSet->check(result, warnMsg); + } + + // Conversions + + inline const osg::ref_ptr<ActionSet> getActionSet() const + { + return _actionSet; + } + + inline const osg::ref_ptr<Instance> getInstance() const + { + return _actionSet->getInstance(); + } + + inline XrInstance getXrInstance() const + { + return _actionSet->getXrInstance(); + } + + inline XrActionSet getXrActionSet() const + { + return _actionSet->getXrActionSet(); + } + + inline XrAction getXrAction() const + { + return _action; + } + + protected: + + // Action data + osg::ref_ptr<ActionSet> _actionSet; + std::vector<XrPath> _subactionPaths; + XrActionCreateInfo _createInfo; + XrAction _action; +}; + +/// Base action state for inputs. +class ActionStateBase : public osg::Referenced +{ + public: + + // Constructors + + ActionStateBase(Action *action, Session *session, + Path subactionPath = Path()); + virtual ~ActionStateBase(); + + // Error checking + + inline bool valid() const + { + return _valid; + } + + inline bool check(XrResult result, const char *warnMsg) const + { + return _action->check(result, warnMsg); + } + + protected: + + // Utilities for synchronisation + + // Find whether the state needs update and update sync counter + bool checkUpdate(); + + // Member data + + osg::ref_ptr<Action> _action; + osg::ref_ptr<Session> _session; + Path _subactionPath; + bool _valid; + unsigned int _syncCount; +}; + +/// All action states have an isActive field. +template <typename T> +class ActionStateCommon : public ActionStateBase +{ + private: + + typedef ActionStateBase Base; + + public: + + // Constructors + + ActionStateCommon(Action *action, Session *session, + Path subactionPath = Path()) : + Base(action, session, subactionPath) + { + } + + // Accessors + + bool isActive() const + { + assert(valid()); + return _state.isActive; + } + + // Operations + + /// Update state if a sync has taken place + bool update() + { + if (Base::checkUpdate()) + return updateState(); + return valid(); + } + + protected: + + // Protected operations + + bool updateState(); + + // Data members + + T _state; +}; + +// These are the base action state classes +typedef ActionStateCommon<XrActionStateBoolean> ActionStateCommonBoolean; +typedef ActionStateCommon<XrActionStateFloat> ActionStateCommonFloat; +typedef ActionStateCommon<XrActionStateVector2f> ActionStateCommonVector2f; +typedef ActionStateCommon<XrActionStatePose> ActionStateCommonPose; + +// Convert action values to app / OSG friendly formats +template <typename T> +struct ActionTypeInfo; + +// XrBool32 -> bool +template <> +struct ActionTypeInfo<XrActionStateBoolean> +{ + static bool convert(XrBool32 value) + { + return value; + } + + static bool defaultValue() + { + return false; + } +}; + +// float -> float +template <> +struct ActionTypeInfo<XrActionStateFloat> +{ + static float convert(float value) + { + return value; + } + + static float defaultValue() + { + return 0.0f; + } +}; + +// XrVector2f -> osg::Vec2f +template <> +struct ActionTypeInfo<XrActionStateVector2f> +{ + static osg::Vec2f convert(const XrVector2f &value) + { + return osg::Vec2f(value.x, value.y); + } + + static osg::Vec2f defaultValue() + { + return osg::Vec2f(0.0f, 0.0f); + } +}; + +/// Some action states have currentValue and related fields. +template <typename T> +class ActionStateSimple : public ActionStateCommon<T> +{ + private: + + typedef ActionStateCommon<T> Base; + + public: + + typedef ActionTypeInfo<T> Info; + + // Constructors + + ActionStateSimple(Action *action, Session *session, + Path subactionPath = Path()) : + Base(action, session, subactionPath) + { + } + + // Accessors + + auto getCurrentState() const + { + assert(this->valid()); + return Info::convert(Base::_state.currentState); + } + + bool hasChangedSinceLastSync() const + { + assert(this->valid()); + return Base::_state.changedSinceLastSync; + } + + XrTime getLastChangedTime() const + { + assert(this->valid()); + return Base::_state.lastChangedTime; + } +}; + +// These are the simple action state classes +typedef ActionStateSimple<XrActionStateBoolean> ActionStateBoolean; +typedef ActionStateSimple<XrActionStateFloat> ActionStateFloat; +typedef ActionStateSimple<XrActionStateVector2f> ActionStateVector2f; + +/// Specialise Action for a specific input type +template <XrActionType type, typename T> +class ActionTyped : public Action +{ + public: + + typedef T State; + + ActionTyped(ActionSet *actionSet, + const std::string &name, + const std::string &localizedName) : + Action(actionSet, name, localizedName, type) + { + } + + osg::ref_ptr<State> createState(Session *session, + Path subactionPath = Path()) + { + return new State(this, session, subactionPath); + } +}; + +// So ActionStatePose etc can take ActionPose etc in constructor +class ActionStatePose; +class ActionStateVibration; + +// These are the final typed action classes +typedef ActionTyped<XR_ACTION_TYPE_BOOLEAN_INPUT, ActionStateBoolean> ActionBoolean; +typedef ActionTyped<XR_ACTION_TYPE_FLOAT_INPUT, ActionStateFloat> ActionFloat; +typedef ActionTyped<XR_ACTION_TYPE_VECTOR2F_INPUT, ActionStateVector2f> ActionVector2f; +typedef ActionTyped<XR_ACTION_TYPE_POSE_INPUT, ActionStatePose> ActionPose; +typedef ActionTyped<XR_ACTION_TYPE_VIBRATION_OUTPUT, ActionStateVibration> ActionVibration; + +/// Pose actions have their own way to get the pose +class ActionStatePose : public ActionStateCommon<XrActionStatePose> +{ + private: + + typedef ActionStateCommon<XrActionStatePose> Base; + + public: + + // Constructors + + ActionStatePose(ActionPose *action, Session *session, + Path subactionPath = Path()); + ~ActionStatePose(); + + // Accessors + + Space *getSpace() + { + return _space.get(); + } + + protected: + + osg::ref_ptr<Space> _space; +}; + +class ActionStateVibration : public osg::Referenced +{ + public: + + // Constructors + + ActionStateVibration(ActionVibration *action, Session *session, + Path subactionPath = Path()); + + // Error checking + + inline bool check(XrResult result, const char *warnMsg) const + { + return _action->check(result, warnMsg); + } + + // Haptic vibrations + + bool applyHapticFeedback(int64_t duration_ns, float frequency, + float amplitude) const; + bool stopHapticFeedback() const; + + protected: + + // Member data + + osg::ref_ptr<Action> _action; + osg::ref_ptr<Session> _session; + Path _subactionPath; +}; + +} // osgXR::OpenXR + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/ActionSet.cpp b/3rdparty/osgXR/src/OpenXR/ActionSet.cpp new file mode 100644 index 000000000..a4462b555 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/ActionSet.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "ActionSet.h" + +#include <cstring> + +using namespace osgXR::OpenXR; + +ActionSet::ActionSet(Instance *instance, + const std::string &name, + const std::string &localizedName, + uint32_t priority) : + _instance(instance), + _actionSet(XR_NULL_HANDLE) +{ + XrActionSetCreateInfo createInfo{ XR_TYPE_ACTION_SET_CREATE_INFO }; + strncpy(createInfo.actionSetName, name.c_str(), + XR_MAX_ACTION_SET_NAME_SIZE - 1); + strncpy(createInfo.localizedActionSetName, localizedName.c_str(), + XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE - 1); + createInfo.priority = priority; + + check(xrCreateActionSet(getXrInstance(), &createInfo, &_actionSet), + "Failed to create OpenXR action set"); +} + +ActionSet::~ActionSet() +{ + if (_actionSet != XR_NULL_HANDLE) + { + check(xrDestroyActionSet(_actionSet), + "Failed to destroy OpenXR action set"); + } +} diff --git a/3rdparty/osgXR/src/OpenXR/ActionSet.h b/3rdparty/osgXR/src/OpenXR/ActionSet.h new file mode 100644 index 000000000..34966fde8 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/ActionSet.h @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_ACTION_SET +#define OSGXR_OPENXR_ACTION_SET 1 + +#include "Instance.h" + +#include <osg/ref_ptr> + +#include <cstdint> +#include <string> + +namespace osgXR { + +namespace OpenXR { + +class ActionSet : public osg::Referenced +{ + public: + + ActionSet(Instance *instance, + const std::string &name, + const std::string &localizedName, + uint32_t priority); + virtual ~ActionSet(); + + // Error checking + + inline bool valid() const + { + return _actionSet != XR_NULL_HANDLE; + } + + inline bool check(XrResult result, const char *warnMsg) const + { + return _instance->check(result, warnMsg); + } + + // Conversions + + inline const osg::ref_ptr<Instance> getInstance() const + { + return _instance; + } + + inline XrInstance getXrInstance() const + { + return _instance->getXrInstance(); + } + + inline XrActionSet getXrActionSet() const + { + return _actionSet; + } + + + protected: + + // Action set data + osg::ref_ptr<Instance> _instance; + XrActionSet _actionSet; +}; + +} // osgXR::OpenXR + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/Compositor.cpp b/3rdparty/osgXR/src/OpenXR/Compositor.cpp new file mode 100644 index 000000000..df347eda7 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/Compositor.cpp @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "Compositor.h" +#include "DepthInfo.h" +#include "Space.h" +#include "SwapchainGroupSubImage.h" + +#include <cassert> + +using namespace osgXR::OpenXR; + +void CompositionLayerProjection::addView(osg::ref_ptr<Session::Frame> frame, uint32_t viewIndex, + const SwapchainGroup::SubImage &subImage, + const DepthInfo *depthInfo) +{ + assert(viewIndex < _projViews.size()); + + XrCompositionLayerProjectionView &projView = _projViews[viewIndex]; + projView = { XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW }; + projView.pose = frame->getViewPose(viewIndex); + projView.fov = frame->getViewFov(viewIndex); + subImage.getXrSubImage(&projView.subImage); + + if (depthInfo && subImage.depthValid()) + { + // depth info + XrCompositionLayerDepthInfoKHR &xrDepthInfo = _depthInfos[viewIndex]; + xrDepthInfo = { XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR }; + subImage.getDepthXrSubImage(&xrDepthInfo.subImage); + xrDepthInfo.minDepth = depthInfo->getMinDepth(); + xrDepthInfo.maxDepth = depthInfo->getMaxDepth(); + xrDepthInfo.nearZ = depthInfo->getNearZ(); + xrDepthInfo.farZ = depthInfo->getFarZ(); + + // add depth info to projection view chain + projView.next = &xrDepthInfo; + } +} + +const XrCompositionLayerBaseHeader *CompositionLayerProjection::getXr() +{ + unsigned int validDepthInfos = 0; + for (unsigned int i = 0; i < _projViews.size(); ++i) + { + auto &view = _projViews[i]; + auto &depthInfo = _depthInfos[i]; + if (view.type != XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW) + { + // Eek, some views have been omitted! + OSG_WARN << "Partial projection views!" << std::endl; + } + + if (depthInfo.type == XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR) + ++validDepthInfos; + } + + // Sanity check that depth info is entirely missing or complete + if (validDepthInfos > 0 && validDepthInfos < _projViews.size()) + { + OSG_WARN << "Partial projection depth info, disabling depth information" << std::endl; + for (auto &view: _projViews) + view.next = nullptr; + } + + _layer.layerFlags = _layerFlags; + _layer.space = _space->getXrSpace(); + _layer.viewCount = _projViews.size(); + _layer.views = _projViews.data(); + return reinterpret_cast<const XrCompositionLayerBaseHeader*>(&_layer); +} diff --git a/3rdparty/osgXR/src/OpenXR/Compositor.h b/3rdparty/osgXR/src/OpenXR/Compositor.h new file mode 100644 index 000000000..a8bbd2d1a --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/Compositor.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_COMPOSITOR +#define OSGXR_OPENXR_COMPOSITOR 1 + +#include "Session.h" +#include "Space.h" +#include "SwapchainGroup.h" + +#include <osg/Referenced> +#include <osg/ref_ptr> + +namespace osgXR { + +namespace OpenXR { + +class DepthInfo; + +class CompositionLayer : public osg::Referenced +{ + public: + + CompositionLayer() : + _layerFlags(0) + { + } + + virtual ~CompositionLayer() + { + } + + inline XrCompositionLayerFlags getLayerFlags() const + { + return _layerFlags; + } + inline void setLayerFlags(XrCompositionLayerFlags layerFlags) + { + _layerFlags = layerFlags; + } + + inline Space *getSpace() const + { + return _space; + } + inline void setSpace(Space *space) + { + _space = space; + } + + virtual const XrCompositionLayerBaseHeader *getXr() = 0; + + protected: + + XrCompositionLayerFlags _layerFlags; + osg::ref_ptr<Space> _space; +}; + +class CompositionLayerProjection : public CompositionLayer +{ + public: + + CompositionLayerProjection(unsigned int viewCount) + { + _layer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION; + _layer.next = nullptr; + _projViews.resize(viewCount); + _depthInfos.resize(viewCount); + } + + virtual ~CompositionLayerProjection() + { + } + + void addView(osg::ref_ptr<Session::Frame> frame, uint32_t viewIndex, + const SwapchainGroup::SubImage &subImage, + const DepthInfo *depthInfo = nullptr); + + const XrCompositionLayerBaseHeader *getXr() override; + + protected: + + mutable XrCompositionLayerProjection _layer; + std::vector<XrCompositionLayerProjectionView> _projViews; + std::vector<XrCompositionLayerDepthInfoKHR> _depthInfos; +}; + +} // osgXR::OpenXR + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/DepthInfo.h b/3rdparty/osgXR/src/OpenXR/DepthInfo.h new file mode 100644 index 000000000..392c3eafc --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/DepthInfo.h @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_DEPTH_INFO +#define OSGXR_OPENXR_DEPTH_INFO 1 + +#include <osg/Matrixd> + +namespace osgXR { + +namespace OpenXR { + +// Represents depth information for a view +class DepthInfo +{ + public: + + DepthInfo() : + _minDepth(0), + _maxDepth(1), + _nearZ(1), + _farZ(10) + { + } + + // Mutators + + void setDepthRange(float minDepth, float maxDepth) + { + _minDepth = minDepth; + _maxDepth = maxDepth; + } + + void setZRange(float nearZ, float farZ) + { + _nearZ = nearZ; + _farZ = farZ; + } + + void setZRangeFromProjection(const osg::Matrixd &proj) + { + float left, right, bottom, top; + proj.getFrustum(left, right, bottom, top, _nearZ, _farZ); + } + + // Accessors + + float getMinDepth() const + { + return _minDepth; + } + + float getMaxDepth() const + { + return _maxDepth; + } + + float getNearZ() const + { + return _nearZ; + } + + float getFarZ() const + { + return _farZ; + } + + protected: + + float _minDepth; + float _maxDepth; + float _nearZ; + float _farZ; +}; + +} // osgXR::OpenXR + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/EventHandler.cpp b/3rdparty/osgXR/src/OpenXR/EventHandler.cpp new file mode 100644 index 000000000..9ec58c53e --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/EventHandler.cpp @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "EventHandler.h" +#include "Instance.h" +#include "Session.h" + +#include <osg/Notify> + +using namespace osgXR::OpenXR; + +void EventHandler::onEvent(Instance *instance, + const XrEventDataBuffer *event) +{ + switch (event->type) + { + case XR_TYPE_EVENT_DATA_EVENTS_LOST: + onEventsLost(instance, + reinterpret_cast<const XrEventDataEventsLost *>(event)); + break; + case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: + onInstanceLossPending(instance, + reinterpret_cast<const XrEventDataInstanceLossPending *>(event)); + break; + case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: + { + auto *profileEvent = reinterpret_cast<const XrEventDataInteractionProfileChanged *>(event); + Session *session = instance->getSession(profileEvent->session); + if (session) + onInteractionProfileChanged(session, profileEvent); + else + OSG_WARN << "Unhandled OpenXR interaction profile changed event: Session not registered" << std::endl; + break; + } + case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: + { + auto *spaceEvent = reinterpret_cast<const XrEventDataReferenceSpaceChangePending *>(event); + Session *session = instance->getSession(spaceEvent->session); + if (session) + onReferenceSpaceChangePending(session, spaceEvent); + else + OSG_WARN << "Unhandled OpenXR reference space change pending event: Session not registered" << std::endl; + break; + } + case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR: + { + auto *maskEvent = reinterpret_cast<const XrEventDataVisibilityMaskChangedKHR *>(event); + Session *session = instance->getSession(maskEvent->session); + if (session) + onVisibilityMaskChanged(session, maskEvent); + else + OSG_WARN << "Unhandled OpenXR visibility mask change event: Session not registered" << std::endl; + break; + } + break; + case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: + { + auto *stateEvent = reinterpret_cast<const XrEventDataSessionStateChanged *>(event); + Session *session = instance->getSession(stateEvent->session); + if (session) + onSessionStateChanged(session, stateEvent); + else + OSG_WARN << "Unhandled OpenXR session state change event: Session not registered" << std::endl; + break; + } + default: + onUnhandledEvent(instance, event); + break; + } +} + +void EventHandler::onUnhandledEvent(Instance *instance, + const XrEventDataBuffer *event) +{ + OSG_WARN << "Unhandled OpenXR Event: " << event->type << std::endl; +} + +void EventHandler::onEventsLost(Instance *instance, + const XrEventDataEventsLost *event) +{ + OSG_WARN << event->lostEventCount << " OpenXR events lost" << std::endl; +} + +void EventHandler::onInstanceLossPending(Instance *instance, + const XrEventDataInstanceLossPending *event) +{ + OSG_WARN << "OpenXR instance loss pending" << std::endl; +} + +void EventHandler::onInteractionProfileChanged(Session *session, + const XrEventDataInteractionProfileChanged *event) +{ + OSG_WARN << "OpenXR interaction profile changed" << std::endl; +} + +void EventHandler::onReferenceSpaceChangePending(Session *session, + const XrEventDataReferenceSpaceChangePending *event) +{ + OSG_WARN << "OpenXR reference space change penging" << std::endl; +} + +void EventHandler::onVisibilityMaskChanged(Session *session, + const XrEventDataVisibilityMaskChangedKHR *event) +{ + session->updateVisibilityMasks(event->viewConfigurationType, + event->viewIndex); +} + +void EventHandler::onSessionStateChanged(Session *session, + const XrEventDataSessionStateChanged *event) +{ + XrSessionState oldState = session->getState(); + session->setState(event->state); + switch (event->state) + { + case XR_SESSION_STATE_IDLE: + // Either starting or soon to be stopping + if (oldState == XR_SESSION_STATE_UNKNOWN) + onSessionStateStart(session); + break; + case XR_SESSION_STATE_READY: + // Session ready to begin + onSessionStateReady(session); + break; + case XR_SESSION_STATE_SYNCHRONIZED: + // Either session synchronised or no longer visible + break; + case XR_SESSION_STATE_VISIBLE: + // Either session now visible or lost focus + if (oldState == XR_SESSION_STATE_FOCUSED) + onSessionStateUnfocus(session); + break; + case XR_SESSION_STATE_FOCUSED: + // Session visible and in focus + onSessionStateFocus(session); + break; + case XR_SESSION_STATE_STOPPING: + // Session now stopping + onSessionStateStopping(session, false); + break; + case XR_SESSION_STATE_LOSS_PENDING: + // Session loss is pending, which can happen at any time + if (oldState == XR_SESSION_STATE_FOCUSED) + onSessionStateUnfocus(session); + if (session->isRunning()) + onSessionStateStopping(session, true); + // Attempt restart + onSessionStateEnd(session, true); + break; + case XR_SESSION_STATE_EXITING: + // Session is exiting and should be cleaned up + onSessionStateEnd(session, false); + break; + default: + OSG_WARN << "Unknown OpenXR session state: " << event->state << std::endl; + break; + } +} + +void EventHandler::onSessionStateStart(Session *session) +{ +} + +void EventHandler::onSessionStateEnd(Session *session, bool retry) +{ +} + +void EventHandler::onSessionStateReady(Session *session) +{ +} + +void EventHandler::onSessionStateStopping(Session *session, bool loss) +{ +} + +void EventHandler::onSessionStateFocus(Session *session) +{ +} + +void EventHandler::onSessionStateUnfocus(Session *session) +{ +} diff --git a/3rdparty/osgXR/src/OpenXR/EventHandler.h b/3rdparty/osgXR/src/OpenXR/EventHandler.h new file mode 100644 index 000000000..6a4770663 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/EventHandler.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_EVENT_HANDLER +#define OSGXR_OPENXR_EVENT_HANDLER 1 + +#include <osg/Referenced> + +#include <openxr/openxr.h> + +namespace osgXR { + +namespace OpenXR { + +class Instance; +class Session; + +/// This class handles OpenXR events. +class EventHandler : public osg::Referenced +{ + public: + + // Instance events + + /// Top level OpenXR event handler. + void onEvent(Instance *instance, const XrEventDataBuffer *event); + /// Handle an otherwise unhandled event. + virtual void onUnhandledEvent(Instance *instance, + const XrEventDataBuffer *event); + + /// Handle an events lost event. + virtual void onEventsLost(Instance *instance, + const XrEventDataEventsLost *event); + /// Handle an instance loss pending event. + virtual void onInstanceLossPending(Instance *instance, + const XrEventDataInstanceLossPending *event); + + // Session events + + /// Handle an interaction profile changed event. + virtual void onInteractionProfileChanged(Session *session, + const XrEventDataInteractionProfileChanged *event); + /// Handle a reference space change pending event. + virtual void onReferenceSpaceChangePending(Session *session, + const XrEventDataReferenceSpaceChangePending *event); + /// Handle a visibility mask change event. + virtual void onVisibilityMaskChanged(Session *session, + const XrEventDataVisibilityMaskChangedKHR *event); + /// Handle a session state change event. + virtual void onSessionStateChanged(Session *session, + const XrEventDataSessionStateChanged *event); + + // Session state events + + /// Transition into initial idle state (idle, after init). + virtual void onSessionStateStart(Session *session); + /// Transition into ending state (exiting / loss pending, before cleanup). + virtual void onSessionStateEnd(Session *session, bool retry); + + /// Transition into a ready state. + virtual void onSessionStateReady(Session *session); + /// Transition out of running state (stopping, before end). + virtual void onSessionStateStopping(Session *session, bool loss); + + /// Transition into focused session state. + virtual void onSessionStateFocus(Session *session); + /// Transition out of focused session state. + virtual void onSessionStateUnfocus(Session *session); +}; + +} // osgXR::OpenXR + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/GraphicsBinding.cpp b/3rdparty/osgXR/src/OpenXR/GraphicsBinding.cpp new file mode 100644 index 000000000..b5d26e2d0 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/GraphicsBinding.cpp @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "GraphicsBinding.h" +#include "GraphicsBindingWin32.h" +#include "GraphicsBindingX11.h" + +#include <vector> + +using namespace osgXR::OpenXR; + +namespace osgXR { + +namespace OpenXR { + +class GraphicsBindingProxy : public osg::Referenced +{ + public: + virtual ~GraphicsBindingProxy() {} + + virtual GraphicsBinding *create(osgViewer::GraphicsWindow *window) = 0; +}; + +template <typename GRAPHICS_BINDING> +class GraphicsBindingProxyImpl : public GraphicsBindingProxy +{ + protected: + typedef GRAPHICS_BINDING Binding; + typedef typename Binding::GraphicsWindow Window; + + virtual ~GraphicsBindingProxyImpl() {} + + public: + GraphicsBinding *create(osgViewer::GraphicsWindow *window) override + { + Window *win = dynamic_cast<Window *>(window); + if (!win) + return nullptr; + + return new Binding(win); + } +}; + +} // osgXR::OpenXR + +} // osgXR + +typedef std::vector<osg::ref_ptr<GraphicsBindingProxy> > ProxyList; + +static ProxyList proxies = { +#ifdef OSGXR_USE_WIN32 + new GraphicsBindingProxyImpl<GraphicsBindingWin32>(), +#endif +#ifdef OSGXR_USE_X11 + new GraphicsBindingProxyImpl<GraphicsBindingX11>(), +#endif +}; + +osg::ref_ptr<GraphicsBinding> osgXR::OpenXR::createGraphicsBinding(osgViewer::GraphicsWindow *window) +{ + GraphicsBinding *ret = nullptr; + for (GraphicsBindingProxy *proxy: proxies) + { + ret = proxy->create(window); + if (ret) + break; + } + return ret; +} diff --git a/3rdparty/osgXR/src/OpenXR/GraphicsBinding.h b/3rdparty/osgXR/src/OpenXR/GraphicsBinding.h new file mode 100644 index 000000000..6caca9700 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/GraphicsBinding.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_GRAPHICS_BINDING +#define OSGXR_OPENXR_GRAPHICS_BINDING 1 + +#include <osg/Referenced> +#include <osg/ref_ptr> +#include <osgViewer/GraphicsWindow> + +namespace osgXR { + +namespace OpenXR { + +class GraphicsBinding : public osg::Referenced +{ + public: + virtual ~GraphicsBinding() { } + + virtual void *getXrGraphicsBinding() = 0; +}; + +template <typename GRAPHICS_WINDOW, typename XR_BINDING> +class GraphicsBindingImpl : public GraphicsBinding +{ + public: + typedef GRAPHICS_WINDOW GraphicsWindow; + + GraphicsBindingImpl(GraphicsWindow *window); + virtual ~GraphicsBindingImpl() {} + + void *getXrGraphicsBinding() override + { + return &_binding; + } + + protected: + + XR_BINDING _binding; +}; + +osg::ref_ptr<GraphicsBinding> createGraphicsBinding(osgViewer::GraphicsWindow *window); + +} // osgXR::OpenXR + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/GraphicsBindingWin32.cpp b/3rdparty/osgXR/src/OpenXR/GraphicsBindingWin32.cpp new file mode 100644 index 000000000..d0f2c52bb --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/GraphicsBindingWin32.cpp @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "GraphicsBindingWin32.h" + +using namespace osgXR::OpenXR; + +template <> +GraphicsBindingWin32::GraphicsBindingImpl(osgViewer::GraphicsWindowWin32 *window) : + _binding{ XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR } +{ + _binding.hDC = window->getHDC(); + _binding.hGLRC = window->getWGLContext(); +} diff --git a/3rdparty/osgXR/src/OpenXR/GraphicsBindingWin32.h b/3rdparty/osgXR/src/OpenXR/GraphicsBindingWin32.h new file mode 100644 index 000000000..d7ed1e643 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/GraphicsBindingWin32.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_GRAPHICS_BINDING_WIN32 +#define OSGXR_OPENXR_GRAPHICS_BINDING_WIN32 1 + +#ifdef OSGXR_USE_WIN32 + +#include "GraphicsBinding.h" + +#include <osgViewer/api/Win32/GraphicsWindowWin32> + +#define XR_USE_GRAPHICS_API_OPENGL +#define XR_USE_PLATFORM_WIN32 +#include <openxr/openxr_platform.h> + +namespace osgXR { + +namespace OpenXR { + +typedef GraphicsBindingImpl<osgViewer::GraphicsWindowWin32, XrGraphicsBindingOpenGLWin32KHR> GraphicsBindingWin32; + +} // osgXR::OpenXR + +} // osgXR + +#endif // OSGXR_USE_WIN32 + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/GraphicsBindingX11.cpp b/3rdparty/osgXR/src/OpenXR/GraphicsBindingX11.cpp new file mode 100644 index 000000000..8889d4e7e --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/GraphicsBindingX11.cpp @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "GraphicsBindingX11.h" + +using namespace osgXR::OpenXR; + +namespace { + +/// Class to spy on protected members of GraphicsWindowX11. +class GraphicsWindowX11Spy : public osgViewer::GraphicsWindowX11 +{ +public: + const XVisualInfo *getVisualInfo() const + { + return _visualInfo; + } + + const GLXFBConfig &getFBConfig() const + { + return _fbConfig; + } +}; + +} + +template <> +GraphicsBindingX11::GraphicsBindingImpl(osgViewer::GraphicsWindowX11 *window) : + _binding{ XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR } +{ + // window isn't actually of type GraphicsWindowX11Spy, but this allows us to + // spy on protected members that don't have public accessors. + auto spyWindow = static_cast<GraphicsWindowX11Spy *>(window); + + _binding.xDisplay = window->getDisplay(); + _binding.visualid = spyWindow->getVisualInfo()->visualid; + _binding.glxFBConfig = spyWindow->getFBConfig(); + _binding.glxDrawable = window->getWindow(); + _binding.glxContext = window->getContext(); +} diff --git a/3rdparty/osgXR/src/OpenXR/GraphicsBindingX11.h b/3rdparty/osgXR/src/OpenXR/GraphicsBindingX11.h new file mode 100644 index 000000000..bfba987a6 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/GraphicsBindingX11.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_GRAPHICS_BINDING_X11 +#define OSGXR_OPENXR_GRAPHICS_BINDING_X11 1 + +#ifdef OSGXR_USE_X11 + +#include "GraphicsBinding.h" + +#include <osgViewer/api/X11/GraphicsWindowX11> + +#define XR_USE_GRAPHICS_API_OPENGL +#define XR_USE_PLATFORM_XLIB +#include <openxr/openxr_platform.h> + +namespace osgXR { + +namespace OpenXR { + +typedef GraphicsBindingImpl<osgViewer::GraphicsWindowX11, XrGraphicsBindingOpenGLXlibKHR> GraphicsBindingX11; + +} // osgXR::OpenXR + +} // osgXR + +#endif // OSGXR_USE_X11 + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/Instance.cpp b/3rdparty/osgXR/src/OpenXR/Instance.cpp new file mode 100644 index 000000000..329250e7f --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/Instance.cpp @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "EventHandler.h" +#include "Instance.h" +#include "Session.h" +#include "System.h" +#include "generated/Version.h" + +#include <osg/Notify> +#include <osg/Version> +#include <osg/ref_ptr> + +#include <cstring> +#include <vector> + +#define ENGINE_NAME "osgXR" +#define ENGINE_VERSION (OSGXR_MAJOR_VERSION << 16 | \ + OSGXR_MINOR_VERSION << 8 | \ + OSGXR_PATCH_VERSION) +#define API_VERSION XR_MAKE_VERSION(1, 0, 0) + +using namespace osgXR::OpenXR; + +static std::vector<XrApiLayerProperties> layers; +static std::vector<XrExtensionProperties> extensions; + +static bool enumerateLayers(bool invalidate = false) +{ + static bool layersEnumerated = false; + if (invalidate) + { + layers.resize(0); + layersEnumerated = false; + return false; + } + if (layersEnumerated) + { + return true; + } + + // Count layers + uint32_t layerCount = 0; + XrResult res = xrEnumerateApiLayerProperties(0, &layerCount, nullptr); + if (XR_FAILED(res)) + { + OSG_WARN << "Failed to count OpenXR API layers: " << res << std::endl; + return false; + } + + if (layerCount) + { + // Allocate memory + layers.resize(layerCount); + for (auto &layer: layers) + { + layer.type = XR_TYPE_API_LAYER_PROPERTIES; + layer.next = nullptr; + } + + // Enumerate layers + res = xrEnumerateApiLayerProperties(layers.size(), &layerCount, layers.data()); + if (XR_FAILED(res)) + { + OSG_WARN << "Failed to enumerate " << layerCount + << " OpenXR API layers: " << res << std::endl; + return false; + } + + // Layers may change at any time + layers.resize(layerCount); + } + + layersEnumerated = true; + return true; +} + +static bool enumerateExtensions(bool invalidate = false) +{ + static bool extensionsEnumerated = false; + if (invalidate) + { + extensions.resize(0); + extensionsEnumerated = false; + return false; + } + if (extensionsEnumerated) + { + return true; + } + + // Count extensions + uint32_t extensionCount; + XrResult res = xrEnumerateInstanceExtensionProperties(nullptr, 0, &extensionCount, nullptr); + if (XR_FAILED(res)) + { + OSG_WARN << "Failed to count OpenXR instance extensions: " << res << std::endl; + return false; + } + + if (extensionCount) + { + // Allocate memory + extensions.resize(extensionCount); + for (auto &extension: extensions) + { + extension.type = XR_TYPE_EXTENSION_PROPERTIES; + extension.next = nullptr; + } + + // Enumerate extensions + res = xrEnumerateInstanceExtensionProperties(nullptr, extensions.size(), + &extensionCount, extensions.data()); + if (XR_FAILED(res)) + { + OSG_WARN << "Failed to enumerate " << extensionCount + << " OpenXR instance extensions: " << res << std::endl; + return false; + } + + // Extensions may change (?) + extensions.resize(extensionCount); + } + + extensionsEnumerated = true; + return true; +} + +void Instance::invalidateLayers() +{ + enumerateLayers(true); +} + +void Instance::invalidateExtensions() +{ + enumerateExtensions(true); +} + +bool Instance::hasLayer(const char *name) +{ + enumerateLayers(); + + for (auto &layer: layers) + { + if (!strncmp(name, layer.layerName, XR_MAX_API_LAYER_NAME_SIZE)) + { + return true; + } + } + return false; +} + +bool Instance::hasExtension(const char *name) +{ + enumerateExtensions(); + + for (auto &extension: extensions) + { + if (!strncmp(name, extension.extensionName, XR_MAX_EXTENSION_NAME_SIZE)) + { + return true; + } + } + return false; +} + +Instance *Instance::instance() +{ + static osg::ref_ptr<Instance> s_instance = new Instance(); + return s_instance; +} + +Instance::Instance(): + _layerValidation(false), + _depthInfo(false), + _visibilityMask(true), + _instance(XR_NULL_HANDLE), + _lost(false) +{ +} + +Instance::~Instance() +{ + if (_instance != XR_NULL_HANDLE) + { + // Delete the systems + for (System *system: _systems) + { + delete system; + } + + // Destroy the OpenXR instance + XrResult res = xrDestroyInstance(_instance); + if (XR_FAILED(res)) + { + OSG_WARN << "Failed to destroy OpenXR instance" << std::endl; + } + } +} + +Instance::InitResult Instance::init(const char *appName, uint32_t appVersion) +{ + if (_instance != XR_NULL_HANDLE) + { + return INIT_SUCCESS; + } + + std::vector<const char *> layerNames; + std::vector<const char *> extensionNames; + + // Enable validation layer if selected + if (_layerValidation && hasLayer(XR_APILAYER_LUNARG_core_validation)) + { + layerNames.push_back(XR_APILAYER_LUNARG_core_validation); + } + + // We need OpenGL support + if (!hasExtension(XR_KHR_OPENGL_ENABLE_EXTENSION_NAME)) + { + OSG_WARN << "OpenXR runtime doesn't support XR_KHR_opengl_enable extension" << std::endl; + return INIT_FAIL; + } + extensionNames.push_back(XR_KHR_OPENGL_ENABLE_EXTENSION_NAME); + + // Enable depth composition layer support if supported + _supportsCompositionLayerDepth = hasExtension(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME); + if (_depthInfo) + { + if (_supportsCompositionLayerDepth) + extensionNames.push_back(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME); + else + _depthInfo = false; + } + + // Enable visibility mask support if supported + _supportsVisibilityMask = hasExtension(XR_KHR_VISIBILITY_MASK_EXTENSION_NAME); + if (_visibilityMask) + { + if (_supportsVisibilityMask) + extensionNames.push_back(XR_KHR_VISIBILITY_MASK_EXTENSION_NAME); + else + _visibilityMask = false; + } + + // Create the instance + XrInstanceCreateInfo info{ XR_TYPE_INSTANCE_CREATE_INFO }; + strncpy(info.applicationInfo.applicationName, appName, + XR_MAX_APPLICATION_NAME_SIZE - 1); + info.applicationInfo.applicationVersion = appVersion; + strncpy(info.applicationInfo.engineName, ENGINE_NAME, + XR_MAX_ENGINE_NAME_SIZE - 1); + info.applicationInfo.engineVersion = ENGINE_VERSION; + info.applicationInfo.apiVersion = API_VERSION; + info.enabledApiLayerCount = layerNames.size(); + info.enabledApiLayerNames = layerNames.data(); + info.enabledExtensionCount = extensionNames.size(); + info.enabledExtensionNames = extensionNames.data(); + + XrResult res = xrCreateInstance(&info, &_instance); + if (XR_FAILED(res)) + { + OSG_WARN << "Failed to create OpenXR instance: " << res << std::endl; + if (res == XR_ERROR_INSTANCE_LOST) + return INIT_LATER; + return INIT_FAIL; + } + + // Log the runtime properties + _properties.type = XR_TYPE_INSTANCE_PROPERTIES; + _properties.next = nullptr; + + if (XR_SUCCEEDED(xrGetInstanceProperties(_instance, &_properties))) + { + OSG_INFO << "OpenXR Runtime: \"" << _properties.runtimeName + << "\" version " << XR_VERSION_MAJOR(_properties.runtimeVersion) + << "." << XR_VERSION_MINOR(_properties.runtimeVersion) + << "." << XR_VERSION_PATCH(_properties.runtimeVersion) << std::endl; + } + + // Get extension functions + _xrGetOpenGLGraphicsRequirementsKHR = (PFN_xrGetOpenGLGraphicsRequirementsKHR)getProcAddr("xrGetOpenGLGraphicsRequirementsKHR"); + if (_visibilityMask) + _xrGetVisibilityMaskKHR = (PFN_xrGetVisibilityMaskKHR)getProcAddr("xrGetVisibilityMaskKHR"); + + return INIT_SUCCESS; +} + +bool Instance::check(XrResult result, const char *warnMsg) const +{ + if (XR_FAILED(result)) + { + if (result == XR_ERROR_INSTANCE_LOST) + _lost = true; + + char resultName[XR_MAX_RESULT_STRING_SIZE]; + if (XR_FAILED(xrResultToString(_instance, result, resultName))) + { + OSG_WARN << warnMsg << ": " << result << std::endl; + } + else + { + OSG_WARN << warnMsg << ": " << resultName << std::endl; + } + return false; + } + return true; +} + +PFN_xrVoidFunction Instance::getProcAddr(const char *name) const +{ + PFN_xrVoidFunction ret = nullptr; + check(xrGetInstanceProcAddr(_instance, name, &ret), + "Failed to get OpenXR procedure address"); + return ret; +} + +System *Instance::getSystem(XrFormFactor formFactor, bool *supported) +{ + unsigned long ffId = formFactor - 1; + if (ffId < _systems.size() && _systems[ffId]) + { + if (supported) + *supported = true; + return _systems[ffId]; + } + + XrSystemGetInfo getInfo{ XR_TYPE_SYSTEM_GET_INFO }; + getInfo.formFactor = formFactor; + + XrSystemId systemId; + XrResult res = xrGetSystem(_instance, &getInfo, &systemId); + if (res == XR_ERROR_FORM_FACTOR_UNAVAILABLE) + { + // The system is only *TEMPORARILY* unavailable + if (supported) + *supported = true; + return nullptr; + } + else if (check(res, "Failed to get OpenXR system")) + { + if (ffId >= _systems.size()) + _systems.resize(ffId+1, nullptr); + + if (supported) + *supported = true; + return _systems[ffId] = new System(this, systemId); + } + + if (supported) + *supported = false; + return nullptr; +} + +void Instance::invalidateSystem(XrFormFactor formFactor) +{ + unsigned long ffId = formFactor - 1; + if (ffId < _systems.size()) + { + delete _systems[ffId]; + _systems[ffId] = nullptr; + } +} + +void Instance::registerSession(Session *session) +{ + _sessions[session->getXrSession()] = session; +} + +void Instance::unregisterSession(Session *session) +{ + _sessions.erase(session->getXrSession()); +} + +Session *Instance::getSession(XrSession xrSession) +{ + auto it = _sessions.find(xrSession); + if (it == _sessions.end()) + return nullptr; + return (*it).second; +} + +void Instance::pollEvents(EventHandler *handler) +{ + for (;;) + { + XrEventDataBuffer event; + event.type = XR_TYPE_EVENT_DATA_BUFFER; + event.next = nullptr; + + XrResult res = xrPollEvent(_instance, &event); + if (XR_FAILED(res)) + break; + if (res == XR_EVENT_UNAVAILABLE) + break; + + handler->onEvent(this, &event); + } +} diff --git a/3rdparty/osgXR/src/OpenXR/Instance.h b/3rdparty/osgXR/src/OpenXR/Instance.h new file mode 100644 index 000000000..bf1da19fe --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/Instance.h @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_INSTANCE +#define OSGXR_OPENXR_INSTANCE 1 + +#include <map> +#include <vector> + +#include <osg/Referenced> +#include <osg/observer_ptr> + +#include <openxr/openxr.h> +#define XR_USE_GRAPHICS_API_OPENGL +#include <openxr/openxr_platform.h> + +#define XR_APILAYER_LUNARG_core_validation "XR_APILAYER_LUNARG_core_validation" + +namespace osgXR { + +namespace OpenXR { + +class EventHandler; +class System; +class Session; + +class Instance : public osg::Referenced +{ + public: + + static Instance *instance(); + + Instance(); + virtual ~Instance(); + + // Layers and extensions + + static void invalidateLayers(); + static void invalidateExtensions(); + static bool hasLayer(const char *name); + static bool hasExtension(const char *name); + + // Instance initialisation + + void setValidationLayer(bool layerValidation) + { + _layerValidation = layerValidation; + } + + void setDepthInfo(bool depthInfo) + { + _depthInfo = depthInfo; + } + + void setVisibilityMask(bool visibilityMask) + { + _visibilityMask = visibilityMask; + } + + typedef enum { + /// Instance creation successful. + INIT_SUCCESS, + /// Instance creation not possible at the moment, try again later. + INIT_LATER, + /// Instance creation failed. + INIT_FAIL, + } InitResult; + InitResult init(const char *appName, uint32_t appVersion); + + // Error checking + + inline bool valid() const + { + return _instance != XR_NULL_SYSTEM_ID; + } + + inline bool lost() const + { + return _lost; + } + + bool check(XrResult result, const char *warnMsg) const; + + // Conversions + + inline XrInstance getXrInstance() const + { + return _instance; + } + + // Instance properties + inline const char *getRuntimeName() const + { + return _properties.runtimeName; + } + + // Extensions + + bool supportsCompositionLayerDepth() const + { + return _supportsCompositionLayerDepth; + } + + bool supportsVisibilityMask() const + { + return _supportsVisibilityMask; + } + + PFN_xrVoidFunction getProcAddr(const char *name) const; + + XrResult getOpenGLGraphicsRequirements(XrSystemId systemId, + XrGraphicsRequirementsOpenGLKHR* graphicsRequirements) const + { + if (!_xrGetOpenGLGraphicsRequirementsKHR) + return XR_ERROR_FUNCTION_UNSUPPORTED; + return _xrGetOpenGLGraphicsRequirementsKHR(_instance, systemId, + graphicsRequirements); + } + + XrResult xrGetVisibilityMask(XrSession session, + XrViewConfigurationType viewConfigurationType, + uint32_t viewIndex, + XrVisibilityMaskTypeKHR visibilityMaskType, + XrVisibilityMaskKHR *visibilityMask) + { + if (!_xrGetVisibilityMaskKHR) + return XR_ERROR_FUNCTION_UNSUPPORTED; + return _xrGetVisibilityMaskKHR(session, viewConfigurationType, + viewIndex, visibilityMaskType, + visibilityMask); + } + + // Queries + + System *getSystem(XrFormFactor formFactor, bool *supported = nullptr); + + // Up to caller to ensure no session + void invalidateSystem(XrFormFactor formFactor); + void registerSession(Session *session); + void unregisterSession(Session *session); + Session *getSession(XrSession xrSession); + + // Events + + void pollEvents(EventHandler *handler); + + protected: + + // Setup data + bool _layerValidation; + bool _depthInfo; + bool _visibilityMask; + + // Instance data + XrInstance _instance; + mutable bool _lost; + + // Extension presence + bool _supportsCompositionLayerDepth; + bool _supportsVisibilityMask; + // Extension functions + mutable PFN_xrGetOpenGLGraphicsRequirementsKHR _xrGetOpenGLGraphicsRequirementsKHR; + mutable PFN_xrGetVisibilityMaskKHR _xrGetVisibilityMaskKHR; + + // Instance properties + XrInstanceProperties _properties; + + // Systems + mutable std::vector<System *> _systems; + + // Sessions + std::map<XrSession, Session *> _sessions; +}; + +} // osgXR::OpenXR + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/InteractionProfile.cpp b/3rdparty/osgXR/src/OpenXR/InteractionProfile.cpp new file mode 100644 index 000000000..41d066697 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/InteractionProfile.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "InteractionProfile.h" + +#include <cassert> +#include <vector> + +using namespace osgXR::OpenXR; + +InteractionProfile::InteractionProfile(const Path &path) : + _path(path) +{ +} + +InteractionProfile::InteractionProfile(Instance *instance, + const char *vendor, const char *type) : + _path(instance, (std::string)"/interaction_profiles/" + vendor + "/" + type) +{ +} + +InteractionProfile::~InteractionProfile() +{ +} + +void InteractionProfile::addBinding(Action *action, const Path &binding) +{ + assert(binding.getInstance() == getInstance()); + _bindings.insert(ActionBindingPair(action, binding.getXrPath())); +} + +bool InteractionProfile::suggestBindings() +{ + // No bindings: nothing to do! + if (_bindings.empty()) + return true; + + // Construct binding vector from _bindings map + std::vector<XrActionSuggestedBinding> bindings; + bindings.reserve(_bindings.size()); + for (auto pair: _bindings) + { + if (pair.first->init()) + bindings.push_back({ pair.first->getXrAction(), + pair.second }); + } + + // Suggest the bindings + XrInteractionProfileSuggestedBinding suggestedBinding{ + XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING + }; + suggestedBinding.interactionProfile = _path.getXrPath(); + suggestedBinding.countSuggestedBindings = bindings.size(); + suggestedBinding.suggestedBindings = bindings.data(); + + return check(xrSuggestInteractionProfileBindings(getXrInstance(), + &suggestedBinding), + "Failed to suggest interaction profile bindings"); +} diff --git a/3rdparty/osgXR/src/OpenXR/InteractionProfile.h b/3rdparty/osgXR/src/OpenXR/InteractionProfile.h new file mode 100644 index 000000000..525eda328 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/InteractionProfile.h @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_INTERACTION_PROFILE +#define OSGXR_OPENXR_INTERACTION_PROFILE 1 + +#include "Action.h" +#include "Path.h" + +#include <osg/ref_ptr> + +#include <set> +#include <utility> + +namespace osgXR { + +namespace OpenXR { + +class InteractionProfile : public osg::Referenced +{ + public: + + InteractionProfile(const Path &path); + InteractionProfile(Instance *instance, + const char *vendor, const char *type); + virtual ~InteractionProfile(); + + // Accessors + + void addBinding(Action *action, const std::string &binding) + { + Path path(_path.getInstance(), binding); + addBinding(action, path); + } + + void addBinding(Action *action, const Path &binding); + + // returns true on success + bool suggestBindings(); + + // Error checking + + inline bool check(XrResult result, const char *warnMsg) const + { + return _path.check(result, warnMsg); + } + + // Conversions + + inline const osg::ref_ptr<Instance> getInstance() const + { + return _path.getInstance(); + } + + inline XrInstance getXrInstance() const + { + return _path.getXrInstance(); + } + + inline const Path &getPath() const + { + return _path; + } + + protected: + + // Interaction profile data + Path _path; + typedef std::pair<osg::ref_ptr<Action>, XrPath> ActionBindingPair; + std::set<ActionBindingPair> _bindings; +}; + +} // osgXR::OpenXR + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/Path.cpp b/3rdparty/osgXR/src/OpenXR/Path.cpp new file mode 100644 index 000000000..2a1293ee4 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/Path.cpp @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "Path.h" + +using namespace osgXR::OpenXR; + +Path::Path(Instance *instance, + XrPath path) : + _instance(instance), + _path(path) +{ +} + +Path::Path(Instance *instance, + const std::string &path) : + _instance(instance), + _path(XR_NULL_PATH) +{ + check(xrStringToPath(getXrInstance(), path.c_str(), &_path), + "Failed to create OpenXR path from string"); +} + +std::string Path::toString() const +{ + if (!valid()) + return ""; + + uint32_t count; + if (!check(xrPathToString(getXrInstance(), _path, + 0, &count, nullptr), + "Failed to size OpenXR path string")) + return ""; + std::vector<char> buffer(count); + if (!check(xrPathToString(getXrInstance(), _path, + buffer.size(), &count, buffer.data()), + "Failed to get OpenXR path string")) + return ""; + + return buffer.data(); +} diff --git a/3rdparty/osgXR/src/OpenXR/Path.h b/3rdparty/osgXR/src/OpenXR/Path.h new file mode 100644 index 000000000..6675067b7 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/Path.h @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_PATH +#define OSGXR_OPENXR_PATH 1 + +#include "Instance.h" + +#include <osg/ref_ptr> + +#include <string> + +namespace osgXR { + +namespace OpenXR { + +class Path +{ + public: + + Path(Instance *instance = nullptr, XrPath path = XR_NULL_PATH); + Path(Instance *instance, const std::string &path); + + // Error checking + + inline bool valid() const + { + return _path != XR_NULL_PATH; + } + + inline bool check(XrResult result, const char *warnMsg) const + { + return _instance->check(result, warnMsg); + } + + // Conversions + + inline const osg::ref_ptr<Instance> getInstance() const + { + return _instance; + } + + inline XrInstance getXrInstance() const + { + return _instance->getXrInstance(); + } + + inline XrPath getXrPath() const + { + return _path; + } + + std::string toString() const; + + // Comparisons + + bool operator == (const Path &other) const + { + return _path == other._path && + _instance == other._instance; + } + + bool operator != (const Path &other) const + { + return _path != other._path || + _instance != other._instance; + } + + protected: + + // Path data + osg::ref_ptr<Instance> _instance; + XrPath _path; +}; + +} // osgXR::OpenXR + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/Session.cpp b/3rdparty/osgXR/src/OpenXR/Session.cpp new file mode 100644 index 000000000..82ec24db8 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/Session.cpp @@ -0,0 +1,519 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#define XR_USE_GRAPHICS_API_OPENGL +#include <openxr/openxr_platform.h> + +#include "ActionSet.h" +#include "Compositor.h" +#include "Session.h" +#include "GraphicsBinding.h" + +#include <osg/Notify> + +#include <cassert> +#include <vector> + +#ifdef OSGXR_USE_X11 +#include <osgViewer/api/X11/GraphicsWindowX11> +#include <GL/glx.h> +#endif // OSGXR_USE_X11 + +using namespace osgXR::OpenXR; + +Session::Session(System *system, + osgViewer::GraphicsWindow *window) : + _window(window), + _instance(system->getInstance()), + _system(system), + _session(XR_NULL_HANDLE), + _viewConfiguration(nullptr), + _actionSyncCount(0), + _state(XR_SESSION_STATE_UNKNOWN), + _running(false), + _exiting(false), + _readSwapchainFormats(false), + _lastDisplayTime(0) +{ + XrSessionCreateInfo createInfo = { XR_TYPE_SESSION_CREATE_INFO }; + createInfo.systemId = getXrSystemId(); + + // Get OpenGL graphics requirements + XrGraphicsRequirementsOpenGLKHR req; + req.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR; + req.next = nullptr; + check(_instance->getOpenGLGraphicsRequirements(getXrSystemId(), &req), + "Failed to get OpenXR's OpenGL graphics requirements"); + // ... and pretty much ignore what it says + + osg::ref_ptr<GraphicsBinding> graphicsBinding = createGraphicsBinding(window); + if (graphicsBinding == nullptr) + { + OSG_WARN << "Failed to get OpenXR graphics binding" << std::endl; + return; + } + + createInfo.next = graphicsBinding->getXrGraphicsBinding(); + + // GL context must not be bound in another thread + bool currentSet = checkCurrent(); + // As of 2021-12-16 Monado expects the GL context to be current + // See https://gitlab.freedesktop.org/monado/monado/-/issues/145 + if (!currentSet) + makeCurrent(); + if (check(xrCreateSession(getXrInstance(), &createInfo, &_session), + "Failed to create OpenXR session")) + { + _instance->registerSession(this); + } + if (!currentSet) + releaseContext(); +} + +Session::~Session() +{ + if (_session != XR_NULL_HANDLE) + { + _instance->unregisterSession(this); + _localSpace = nullptr; + // GL context must not be bound in another thread + check(xrDestroySession(_session), + "Failed to destroy OpenXR session"); + } +} + +void Session::addActionSet(ActionSet *actionSet) +{ + assert(actionSet->getInstance() == getInstance()); + _actionSets.insert(actionSet); +} + +bool Session::attachActionSets() +{ + assert(valid()); + if (_actionSets.empty()) + return false; + + // Construct vector of XrActionSets + std::vector<XrActionSet> actionSets; + actionSets.reserve(_actionSets.size()); + for (auto actionSet: _actionSets) + actionSets.push_back(actionSet->getXrActionSet()); + + XrSessionActionSetsAttachInfo attachInfo{ XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO }; + attachInfo.countActionSets = actionSets.size(); + attachInfo.actionSets = actionSets.data(); + + return check(xrAttachSessionActionSets(_session, &attachInfo), + "Failed to attach action sets to OpenXR session"); +} + +Path Session::getCurrentInteractionProfile(const Path &subactionPath) const +{ + XrInteractionProfileState interactionProfile{ XR_TYPE_INTERACTION_PROFILE_STATE }; + + if (check(xrGetCurrentInteractionProfile(_session, subactionPath.getXrPath(), + &interactionProfile), + "Failed to get OpenXR current interaction profile")) + { + return Path(getInstance(), interactionProfile.interactionProfile); + } + return Path(); +} + +bool Session::getActionBoundSources(Action *action, + std::vector<XrPath> &sourcePaths) const +{ + if (!valid()) + return false; + + // Count bound sources + XrBoundSourcesForActionEnumerateInfo enumerateInfo{ XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO }; + enumerateInfo.action = action->getXrAction(); + uint32_t count; + if (check(xrEnumerateBoundSourcesForAction(_session, &enumerateInfo, + 0, &count, nullptr), + "Failed to count OpenXR action bound sources")) + { + // Resize output buffer + sourcePaths.resize(count); + if (!count) + return true; + + // Fill buffer + if (check(xrEnumerateBoundSourcesForAction(_session, &enumerateInfo, + sourcePaths.size(), + &count, + sourcePaths.data()), + "Failed to enumerate OpenXR action bound sources")) + { + // Success! + if (count < sourcePaths.size()) + sourcePaths.resize(count); + return true; + } + } + + // Failure! + return false; +} + +std::string Session::getInputSourceLocalizedName(XrPath sourcePath, + XrInputSourceLocalizedNameFlags whichComponents) const +{ + if (!valid()) + return ""; + + XrInputSourceLocalizedNameGetInfo getInfo{ XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO }; + getInfo.sourcePath = sourcePath; + getInfo.whichComponents = whichComponents; + + uint32_t count; + if (!check(xrGetInputSourceLocalizedName(_session, &getInfo, + 0, &count, nullptr), + "Failed to size OpenXR input source localized name string")) + return ""; + std::vector<char> buffer(count); + if (!check(xrGetInputSourceLocalizedName(_session, &getInfo, + buffer.size(), &count, buffer.data()), + "Failed to get OpenXR input source localized name string")) + return ""; + + return buffer.data(); +} + +void Session::activateActionSet(ActionSet *actionSet, Path subactionPath) +{ + assert(_actionSets.count(actionSet)); + _activeActionSets.insert(ActionSetSubactionPair(actionSet, subactionPath.getXrPath())); +} + +void Session::deactivateActionSet(ActionSet *actionSet, Path subactionPath) +{ + _activeActionSets.erase(ActionSetSubactionPair(actionSet, subactionPath.getXrPath())); +} + +bool Session::syncActions() +{ + assert(valid()); + + XrActionsSyncInfo syncInfo{ XR_TYPE_ACTIONS_SYNC_INFO }; + std::vector<XrActiveActionSet> actionSets; + if (!_activeActionSets.empty()) + { + // Construct vector of XrActionSets + actionSets.reserve(_activeActionSets.size()); + for (auto actionSet: _activeActionSets) + { + XrActiveActionSet activeActionSet; + activeActionSet.actionSet = actionSet.first->getXrActionSet(); + activeActionSet.subactionPath = actionSet.second; + actionSets.push_back(activeActionSet); + } + + syncInfo.countActiveActionSets = actionSets.size(); + syncInfo.activeActionSets = actionSets.data(); + + bool ret = check(xrSyncActions(_session, &syncInfo), + "Failed to sync action sets to OpenXR session"); + if (ret) + ++_actionSyncCount; + return ret; + } + else + { + return false; + } +} + +const Session::SwapchainFormats &Session::getSwapchainFormats() const +{ + if (!_readSwapchainFormats && valid()) + { + uint32_t formatCount; + if (check(xrEnumerateSwapchainFormats(_session, 0, &formatCount, nullptr), + "Failed to count OpenXR swapchain formats")) + { + if (formatCount) + { + _swapchainFormats.resize(formatCount); + if (!check(xrEnumerateSwapchainFormats(_session, formatCount, + &formatCount, _swapchainFormats.data()), + "Failed to enumerate OpenXR swapchain formats")) + { + _swapchainFormats.resize(0); + } + } + } + + _readSwapchainFormats = true; + } + + return _swapchainFormats; +} + +Space *Session::getLocalSpace() +{ + if (!_localSpace.valid()) + _localSpace = new Space(this, XR_REFERENCE_SPACE_TYPE_LOCAL); + + return _localSpace; +} + +void Session::updateVisibilityMasks(XrViewConfigurationType viewConfigurationType, + uint32_t viewIndex) +{ + // Session must be started ... + if (!_viewConfiguration) + return; + // ... and with a matching view configuration + if (viewConfigurationType != _viewConfiguration->getType()) + return; + + if (viewIndex >= _viewConfiguration->getViews().size()) + return; + VisMaskGeometryView &visMaskView = _visMaskCache[viewIndex]; + + // Regenerate cached visibility mask geometries for this viewIndex + for (uint32_t visMaskType = 0; visMaskType < visMaskView.size(); ++visMaskType) + if (visMaskView[visMaskType].valid()) + getVisibilityMask(viewIndex, static_cast<XrVisibilityMaskTypeKHR>(1 + visMaskType), true); +} + +osg::ref_ptr<osg::Geometry> Session::getVisibilityMask(uint32_t viewIndex, + XrVisibilityMaskTypeKHR visibilityMaskType, + bool force) +{ + if (!_viewConfiguration) + return nullptr; + if (viewIndex >= _viewConfiguration->getViews().size()) + return nullptr; + if (visibilityMaskType == 0 || visibilityMaskType > XR_VISIBILITY_MASK_TYPE_LINE_LOOP_KHR) + return nullptr; + + // Size cache to match number of views... + if (_visMaskCache.size() == 0) + _visMaskCache.resize(_viewConfiguration->getViews().size()); + // ... and number of vis mask types + VisMaskGeometryView &visMaskView = _visMaskCache[viewIndex]; + if (visMaskView.size() == 0) + visMaskView.resize(XR_VISIBILITY_MASK_TYPE_LINE_LOOP_KHR); + // Cache hit? + VisMaskGeometry &visMaskGeometry = visMaskView[visibilityMaskType - 1]; + if (!force && visMaskGeometry.valid()) + return visMaskGeometry; + + // Get counts of visibility mask + XrVisibilityMaskKHR visibilityMask{ XR_TYPE_VISIBILITY_MASK_KHR }; + XrResult res = xrGetVisibilityMask(*_viewConfiguration, viewIndex, + visibilityMaskType, &visibilityMask); + if (res != XR_ERROR_FUNCTION_UNSUPPORTED && + check(res, "Failed to size OpenXR visibility mask")) + { + osg::PrimitiveSet::Mode mode; + switch (visibilityMaskType) + { + case XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR: + // fall through + case XR_VISIBILITY_MASK_TYPE_VISIBLE_TRIANGLE_MESH_KHR: + mode = osg::PrimitiveSet::TRIANGLES; + break; + case XR_VISIBILITY_MASK_TYPE_LINE_LOOP_KHR: + mode = osg::PrimitiveSet::LINE_LOOP; + break; + default: + return nullptr; + } + + // Allocate space for data + osg::ref_ptr<osg::Vec2Array> vertices = new osg::Vec2Array(visibilityMask.vertexCountOutput); + osg::ref_ptr<osg::DrawElementsUInt> indices = new osg::DrawElementsUInt(mode, visibilityMask.indexCountOutput); + + // Get the actual data + static_assert(sizeof((*vertices)[0]) == sizeof(XrVector2f)); + static_assert(sizeof((*indices)[0]) == sizeof(uint32_t)); + visibilityMask.vertexCapacityInput = vertices->size(); + visibilityMask.vertices = reinterpret_cast<XrVector2f *>(&vertices->front()); + visibilityMask.indexCapacityInput = indices->size(); + visibilityMask.indices = reinterpret_cast<uint32_t *>(&indices->front()); + XrResult res = xrGetVisibilityMask(*_viewConfiguration, viewIndex, + visibilityMaskType, &visibilityMask); + if (check(res, "Failed to get OpenXR visibility mask")) + { + if (!visMaskGeometry.valid()) + { + // Create a new geometry object + osg::Geometry *geometry = new osg::Geometry(); + geometry->setVertexArray(vertices); + geometry->addPrimitiveSet(indices); + visMaskGeometry = geometry; + return geometry; + } + else + { + // Update the existing geometry object + osg::Geometry *geometry = visMaskGeometry.get(); + geometry->setVertexArray(vertices); + geometry->setPrimitiveSet(0, indices); + return geometry; + } + } + } + + return nullptr; +} + +bool Session::checkCurrent() const +{ +#ifdef OSGXR_USE_X11 + // Ugly X11 specific hack + const auto *window = dynamic_cast<const osgViewer::GraphicsWindowX11*>(_window.get()); + return glXGetCurrentContext() == window->getContext(); +#else + return true; +#endif +} + +void Session::makeCurrent() const +{ +#ifdef OSGXR_USE_X11 + _window->makeCurrentImplementation(); +#endif +} + +void Session::releaseContext() const +{ +#ifdef OSGXR_USE_X11 + _window->releaseContextImplementation(); +#endif +} + +bool Session::begin(const System::ViewConfiguration &viewConfiguration) +{ + _viewConfiguration = &viewConfiguration; + + XrSessionBeginInfo beginInfo{ XR_TYPE_SESSION_BEGIN_INFO }; + beginInfo.primaryViewConfigurationType = viewConfiguration.getType(); + if (check(xrBeginSession(_session, &beginInfo), + "Failed to begin OpenXR session")) + { + _running = true; + return true; + } + return false; +} + +void Session::end() +{ + check(xrEndSession(_session), + "Failed to end OpenXR session"); + _running = false; + _viewConfiguration = nullptr; + _visMaskCache.resize(0); +} + +void Session::requestExit() +{ + _exiting = true; + if (isRunning()) + check(xrRequestExitSession(_session), + "Failed to request OpenXR exit"); +} + +osg::ref_ptr<Session::Frame> Session::waitFrame() +{ + osg::ref_ptr<Frame> frame; + + XrFrameWaitInfo frameWaitInfo{ XR_TYPE_FRAME_WAIT_INFO }; + XrFrameState frameState; + frameState.type = XR_TYPE_FRAME_STATE; + frameState.next = nullptr; + if (check(xrWaitFrame(_session, &frameWaitInfo, &frameState), + "Failed to wait for OpenXR frame")) + { + frame = new Frame(this, &frameState); + _lastDisplayTime = frameState.predictedDisplayTime; + } + + return frame; +} + +Session::Frame::Frame(osg::ref_ptr<Session> session, XrFrameState *frameState) : + _session(session), + _time(frameState->predictedDisplayTime), + _period(frameState->predictedDisplayPeriod), + _shouldRender(frameState->shouldRender), + _osgFrameNumber(0), + _locatedViews(false), + _begun(false), + _envBlendMode(XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM) +{ +} + +Session::Frame::~Frame() +{ +} + +void Session::Frame::locateViews() +{ + // Get view locations + XrViewLocateInfo locateInfo = { XR_TYPE_VIEW_LOCATE_INFO }; + locateInfo.viewConfigurationType = _session->getViewConfiguration()->getType(); + locateInfo.displayTime = _time; + locateInfo.space = _session->getLocalSpace()->getXrSpace(); + + _viewState = { XR_TYPE_VIEW_STATE }; + + uint32_t viewCount; + if (!check(xrLocateViews(_session->getXrSession(), &locateInfo, &_viewState, 0, &viewCount, nullptr), + "Failed to count OpenXR views")) + { + return; + } + _views.resize(viewCount); + for (auto &view: _views) + view = { XR_TYPE_VIEW }; + if (!check(xrLocateViews(_session->getXrSession(), &locateInfo, &_viewState, _views.size(), &viewCount, _views.data()), + "Failed to locate OpenXR views")) + { + return; + } + + _locatedViews = true; +} + +void Session::Frame::addLayer(osg::ref_ptr<CompositionLayer> layer) +{ + _layers.push_back(layer); +} + +bool Session::Frame::begin() +{ + XrFrameBeginInfo frameBeginInfo{ XR_TYPE_FRAME_BEGIN_INFO }; + return _begun = check(xrBeginFrame(_session->getXrSession(), &frameBeginInfo), + "Failed to begin OpenXR frame"); +} + +bool Session::Frame::end() +{ + std::vector<const XrCompositionLayerBaseHeader *> layers; + layers.reserve(_layers.size()); + for (auto &layer: _layers) + layers.push_back(layer->getXr()); + + XrFrameEndInfo frameEndInfo{ XR_TYPE_FRAME_END_INFO }; + frameEndInfo.displayTime = _time; + frameEndInfo.environmentBlendMode = _envBlendMode; + frameEndInfo.layerCount = layers.size(); + frameEndInfo.layers = layers.data(); + + bool currentSet = _session->checkCurrent(); + bool ret = check(xrEndFrame(_session->getXrSession(), &frameEndInfo), + "Failed to end OpenXR frame"); + + // TODO: should not be necessary, but is for SteamVR 1.16.4 (but not 1.15.x) + if (currentSet) + _session->makeCurrent(); + + return ret; +} diff --git a/3rdparty/osgXR/src/OpenXR/Session.h b/3rdparty/osgXR/src/OpenXR/Session.h new file mode 100644 index 000000000..33607a71a --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/Session.h @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_SESSION +#define OSGXR_OPENXR_SESSION 1 + +#include "Path.h" +#include "System.h" + +#include <osg/Geometry> +#include <osg/Referenced> +#include <osg/ref_ptr> +#include <osgViewer/GraphicsWindow> +#include <OpenThreads/Mutex> + +#include <set> + +namespace osgXR { + +namespace OpenXR { + +class Action; +class ActionSet; +class CompositionLayer; +class Space; + +class Session : public osg::Referenced +{ + public: + + // GL context must not be bound in another thread + Session(System *system, osgViewer::GraphicsWindow *window); + // GL context must not be bound in another thread + virtual ~Session(); + + // Error checking + + inline bool valid() const + { + return _session != XR_NULL_HANDLE; + } + + inline bool check(XrResult result, const char *warnMsg) const + { + return _system->check(result, warnMsg); + } + + // Action set attachment + + /// Add an action set to the list. + void addActionSet(ActionSet *actionSet); + /** + * Attach the added action sets to the OpenXR session. + * @return true on success, false on failure. + */ + bool attachActionSets(); + + /// Get the current interaction profile for the given subaction path. + Path getCurrentInteractionProfile(const Path &subactionPath) const; + + /// Get a list of bound source paths for an action. + bool getActionBoundSources(Action *action, + std::vector<XrPath> &sourcePaths) const; + + /** + * Get a localized name for an input source. + * @param sourcePath Input source path. + * @param whichComponents Which components to include. + * @return Localized name string + */ + std::string getInputSourceLocalizedName(XrPath sourcePath, + XrInputSourceLocalizedNameFlags whichComponents) const; + + // Action syncing + + /// Activate a certain action set. + void activateActionSet(ActionSet *actionSet, + Path subactionPath = Path()); + /// Deactivate a certain action set. + void deactivateActionSet(ActionSet *actionSet, + Path subactionPath = Path()); + /// Sync active action sets. + bool syncActions(); + + /// Get the number of action sync counts that have taken place. + unsigned int getActionSyncCount() const + { + return _actionSyncCount; + } + + // Accessors + + // Find whether the session is ready to begin + inline bool isReady() const + { + return _state == XR_SESSION_STATE_READY; + } + + // Find whether the session is running + inline bool isRunning() const + { + return _running; + } + + // Find whether the session is already in the process of exiting + inline bool isExiting() const + { + return _exiting; + } + + inline osgViewer::GraphicsWindow *getWindow() const + { + return _window.get(); + } + + // State management + + inline XrSessionState getState() const + { + return _state; + } + + inline void setState(XrSessionState state) + { + _state = state; + } + + + // Conversions + + inline const osg::ref_ptr<Instance> getInstance() const + { + return _instance; + } + + inline const System *getSystem() const + { + return _system; + } + + inline XrInstance getXrInstance() const + { + return _system->getXrInstance(); + } + + inline XrSystemId getXrSystemId() const + { + return _system->getXrSystemId(); + } + + inline XrSession getXrSession() + { + return _session; + } + + // Queries + + typedef std::vector<int64_t> SwapchainFormats; + const SwapchainFormats &getSwapchainFormats() const; + + Space *getLocalSpace(); + XrTime getLastDisplayTime() const + { + return _lastDisplayTime; + } + + void updateVisibilityMasks(XrViewConfigurationType viewConfigurationType, + uint32_t viewIndex); + osg::ref_ptr<osg::Geometry> getVisibilityMask(uint32_t viewIndex, + XrVisibilityMaskTypeKHR visibilityMaskType, + bool force = false); + + // Operations + + bool checkCurrent() const; + void makeCurrent() const; + void releaseContext() const; + + bool begin(const System::ViewConfiguration &viewConfiguration); + void end(); + void requestExit(); + + const System::ViewConfiguration *getViewConfiguration() const + { + return _viewConfiguration; + } + + class Frame : public osg::Referenced + { + public: + + Frame(osg::ref_ptr<Session> session, XrFrameState *frameState); + + virtual ~Frame(); + + // Error checking + + inline bool check(XrResult result, const char *warnMsg) const + { + return _session->check(result, warnMsg); + } + + // Accessors + + inline bool shouldRender() const + { + return _shouldRender; + } + + inline bool hasBegun() const + { + return _begun; + } + + inline XrTime getTime() const + { + return _time; + } + + void locateViews(); + + void checkLocateViews() + { + OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_locateViewsMutex); + if (!_locatedViews) + locateViews(); + } + + bool isOrientationValid() + { + checkLocateViews(); + return _viewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT; + } + bool isPositionValid() + { + checkLocateViews(); + return _viewState.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT; + } + bool isOrientationTracked() + { + checkLocateViews(); + return _viewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_TRACKED_BIT; + } + bool isPositionTracked() + { + checkLocateViews(); + return _viewState.viewStateFlags & XR_VIEW_STATE_POSITION_TRACKED_BIT; + } + uint32_t getNumViews() + { + checkLocateViews(); + return _views.size(); + } + const XrFovf &getViewFov(uint32_t index) + { + checkLocateViews(); + return _views[index].fov; + } + const XrPosef &getViewPose(uint32_t index) + { + checkLocateViews(); + return _views[index].pose; + } + + // Modifiers + + inline void setEnvBlendMode(XrEnvironmentBlendMode envBlendMode) + { + _envBlendMode = envBlendMode; + } + inline XrEnvironmentBlendMode getEnvBlendMode() const + { + return _envBlendMode; + } + + inline void setOsgFrameNumber(unsigned int osgFrameNumber) + { + _osgFrameNumber = osgFrameNumber; + } + inline unsigned int getOsgFrameNumber() const + { + return _osgFrameNumber; + } + + void addLayer(osg::ref_ptr<CompositionLayer> layer); + + // Operations + + bool begin(); + bool end(); + + protected: + + // Frame info + osg::ref_ptr<Session> _session; + XrTime _time; + XrDuration _period; + bool _shouldRender; + + // OpenSceneGraph frame + unsigned int _osgFrameNumber; + + // For access to _locatedViews etc + OpenThreads::Mutex _locateViewsMutex; + + // View locations (protected by _locateViewsMutex) + bool _locatedViews; + XrViewState _viewState; + std::vector<XrView> _views; + + // Frame end info + bool _begun; + XrEnvironmentBlendMode _envBlendMode; + std::vector<osg::ref_ptr<CompositionLayer> > _layers; + }; + + osg::ref_ptr<Frame> waitFrame(); + + // OpenXR extension wrappers + XrResult xrGetVisibilityMask(const System::ViewConfiguration &viewConfiguration, + uint32_t viewIndex, + XrVisibilityMaskTypeKHR visibilityMaskType, + XrVisibilityMaskKHR *visibilityMask) + { + return _instance->xrGetVisibilityMask(_session, + viewConfiguration.getType(), + viewIndex, visibilityMaskType, + visibilityMask); + } + + protected: + + // Init data + osg::observer_ptr<osgViewer::GraphicsWindow> _window; + + // Session data + osg::ref_ptr<Instance> _instance; + const System *_system; + XrSession _session; + const System::ViewConfiguration *_viewConfiguration; + + // Action sets + std::set<osg::ref_ptr<ActionSet>> _actionSets; + typedef std::pair<ActionSet *, XrPath> ActionSetSubactionPair; + std::set<ActionSetSubactionPair> _activeActionSets; + unsigned int _actionSyncCount; + + // Session state + XrSessionState _state; + bool _running; + bool _exiting; + + // Swapchain formats + mutable bool _readSwapchainFormats; + mutable SwapchainFormats _swapchainFormats; + + // Reference spaces + osg::ref_ptr<Space> _localSpace; + XrTime _lastDisplayTime; + + /* + * Visibility mask geometry cache. + * We keep visibility mask geometries cached to avoid duplication and so + * we can update them after a VisibilityMaskChangedKHR event. + */ + typedef osg::ref_ptr<osg::Geometry> VisMaskGeometry; + typedef std::vector<VisMaskGeometry> VisMaskGeometryView; + typedef std::vector<VisMaskGeometryView> VisMaskGeometries; + VisMaskGeometries _visMaskCache; +}; + +} // osgXR::OpenXR + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/Space.cpp b/3rdparty/osgXR/src/OpenXR/Space.cpp new file mode 100644 index 000000000..03054b9a2 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/Space.cpp @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "Space.h" + +#include <cassert> + +using namespace osgXR::OpenXR; + +static XrPosef poseIdentity = { { 0.0f, 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f, 0.0f } }; + +Space::Space(Session *session, XrReferenceSpaceType type) : + _session(session), + _space(XR_NULL_HANDLE) +{ + // Attempt to create a reference space + XrReferenceSpaceCreateInfo createInfo{ XR_TYPE_REFERENCE_SPACE_CREATE_INFO }; + createInfo.referenceSpaceType = type; + createInfo.poseInReferenceSpace = poseIdentity; + + check(xrCreateReferenceSpace(session->getXrSession(), &createInfo, &_space), + "Failed to create OpenXR reference space"); +} + +Space::Space(Session *session, ActionPose *action, + Path subactionPath) : + _session(session), + _space(XR_NULL_HANDLE) +{ + // Attempt to create an action space for this pose action + XrActionSpaceCreateInfo createInfo{ XR_TYPE_ACTION_SPACE_CREATE_INFO }; + createInfo.action = action->getXrAction(); + createInfo.subactionPath = subactionPath.getXrPath(); + createInfo.poseInActionSpace = poseIdentity; + + check(xrCreateActionSpace(session->getXrSession(), &createInfo, &_space), + "Failed to create OpenXR action space"); +} + +Space::~Space() +{ + if (_session.valid() && valid()) + { + check(xrDestroySpace(_space), + "Failed to destroy OpenXR space"); + } +} + +Space::Location::Location() : + _flags(0) +{ +} + +Space::Location::Location(XrSpaceLocationFlags flags, + const osg::Quat &orientation, + const osg::Vec3f &position) : + _flags(flags), + _orientation(orientation), + _position(position) +{ +} + +bool Space::locate(const Space *baseSpace, XrTime time, + Space::Location &location) +{ + if (!_session.valid() || !valid()) + return false; + assert(_session == baseSpace->_session); + + XrSpaceLocation spaceLocation{ XR_TYPE_SPACE_LOCATION }; + bool ret = check(xrLocateSpace(getXrSpace(), + baseSpace->getXrSpace(), + time, + &spaceLocation), + "Failed to locate OpenXR space"); + if (ret) + { + osg::Quat orientation(spaceLocation.pose.orientation.x, + spaceLocation.pose.orientation.y, + spaceLocation.pose.orientation.z, + spaceLocation.pose.orientation.w); + osg::Vec3f position(spaceLocation.pose.position.x, + spaceLocation.pose.position.y, + spaceLocation.pose.position.z); + location = Location(spaceLocation.locationFlags, + orientation, + position); + } + else + { + location = Location(); + } + return ret; +} diff --git a/3rdparty/osgXR/src/OpenXR/Space.h b/3rdparty/osgXR/src/OpenXR/Space.h new file mode 100644 index 000000000..ac6ef20c2 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/Space.h @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_SPACE +#define OSGXR_OPENXR_SPACE 1 + +#include "Action.h" +#include "Path.h" +#include "Session.h" + +#include <osg/Quat> +#include <osg/Vec3f> +#include <osg/observer_ptr> + +namespace osgXR { + +namespace OpenXR { + +class Space : public osg::Referenced +{ + public: + + /// Create a reference space + Space(Session *session, XrReferenceSpaceType type); + /// Create an action space + Space(Session *session, ActionPose *action, + Path subactionPath = Path()); + virtual ~Space(); + + // Error checking + + inline bool valid() const + { + return _space != XR_NULL_HANDLE; + } + + inline bool check(XrResult result, const char *warnMsg) const + { + return _session->check(result, warnMsg); + } + + // Conversions + + inline XrSpace getXrSpace() const + { + return _space; + } + + // Locating a space + + class Location + { + public: + + // Constructors + + Location(); + Location(XrSpaceLocationFlags flags, + const osg::Quat &orientation, + const osg::Vec3f &position); + + // Error checking + + inline bool valid() const + { + return _flags != 0; + } + + // Accessors + + bool isOrientationValid() const + { + return _flags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT; + } + + bool isPositionValid() const + { + return _flags & XR_SPACE_LOCATION_POSITION_VALID_BIT; + } + + bool isOrientationTracked() const + { + return _flags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT; + } + + bool isPositionTracked() const + { + return _flags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT; + } + + XrSpaceLocationFlags getFlags() const + { + return _flags; + } + + const osg::Quat &getOrientation() const + { + return _orientation; + } + + const osg::Vec3f &getPosition() const + { + return _position; + } + + protected: + + XrSpaceLocationFlags _flags; + osg::Quat _orientation; + osg::Vec3f _position; + }; + + bool locate(const Space *baseSpace, XrTime time, + Location &location); + + protected: + + osg::observer_ptr<Session> _session; + XrSpace _space; +}; + +} // osgXR::OpenXR + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/Swapchain.cpp b/3rdparty/osgXR/src/OpenXR/Swapchain.cpp new file mode 100644 index 000000000..8393ff93b --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/Swapchain.cpp @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include <openxr/openxr.h> + +#include <cassert> + +#include "Swapchain.h" + +using namespace osgXR::OpenXR; + +Swapchain::Swapchain(osg::ref_ptr<Session> session, + const System::ViewConfiguration::View &view, + XrSwapchainUsageFlags usageFlags, + int64_t format) : + _session(session), + _swapchain(XR_NULL_HANDLE), + _width(view.getRecommendedWidth()), + _height(view.getRecommendedHeight()), + _samples(view.getRecommendedSamples()), + _format(format), + _readImageTextures(false) +{ + XrSwapchainCreateInfo createInfo{ XR_TYPE_SWAPCHAIN_CREATE_INFO }; + createInfo.usageFlags = usageFlags; + createInfo.format = format; + createInfo.sampleCount = _samples; + createInfo.width = _width; + createInfo.height = _height; + createInfo.faceCount = 1; + createInfo.arraySize = 1; + createInfo.mipCount = 1; + + bool currentSet = _session->checkCurrent(); + // As of 2021-12-16 Monado expects the GL context to be current + // See https://gitlab.freedesktop.org/monado/monado/-/issues/145 + if (!currentSet) + _session->makeCurrent(); + + // GL context must not be bound in another thread + check(xrCreateSwapchain(getXrSession(), &createInfo, &_swapchain), + "Failed to create OpenXR swapchain"); + + if (currentSet) + // SteamVR 1.16.4 (but not 1.15.x) change context then clear it + _session->makeCurrent(); + else + // SteamVR linux_v1.14 changes context without changing back + // Monado doesn't change it at all (see above) + _session->releaseContext(); +} + +Swapchain::~Swapchain() +{ + if (valid()) + { + // GL context must not be bound in another thread + check(xrDestroySwapchain(_swapchain), + "Failed to destroy OpenXR swapchain"); + } +} + +const Swapchain::ImageTextures &Swapchain::getImageTextures() const +{ + if (!_readImageTextures) + { + // Enumerate the images + uint32_t imageCount; + // GL context must not be bound in another thread + if (check(xrEnumerateSwapchainImages(_swapchain, 0, &imageCount, nullptr), + "Failed to count OpenXR swapchain images")) + { + if (imageCount) + { + std::vector<XrSwapchainImageOpenGLKHR> images(imageCount, { XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR }); + if (check(xrEnumerateSwapchainImages(_swapchain, images.size(), &imageCount, + (XrSwapchainImageBaseHeader *)images.data()), + "Failed to enumerate OpenXR swapchain images")) + { + for (auto image: images) + _imageTextures.push_back(image.image); + } + } + } + + _readImageTextures = true; + } + + return _imageTextures; +} + +osg::ref_ptr<osg::Texture2D> Swapchain::getImageOsgTexture(unsigned int index) const +{ + if (_imageOsgTextures.empty()) + { + getImageTextures(); + _imageOsgTextures.resize(_imageTextures.size()); + } + + assert(index < _imageOsgTextures.size()); + if (!_imageOsgTextures[index].valid()) + { + // Create an OSG texture out of it + osg::Texture2D *texture = new osg::Texture2D; + texture->setTextureSize(getWidth(), + getHeight()); + texture->setInternalFormat(getFormat()); + unsigned int contextID = _session->getWindow()->getState()->getContextID(); + texture->setTextureObject(contextID, new osg::Texture::TextureObject(texture, _imageTextures[index], GL_TEXTURE_2D)); + + _imageOsgTextures[index] = texture; + } + + return _imageOsgTextures[index]; +} + +int Swapchain::acquireImage() const +{ + // Acquire a swapchain image + uint32_t imageIndex; + + bool currentSet = _session->checkCurrent(); + // GL context must not be bound in another thread + if (check(xrAcquireSwapchainImage(_swapchain, nullptr, &imageIndex), + "Failed to acquire swapchain image")) + { + // TODO: should not be necessary, but is for SteamVR 1.16.4 (but not 1.15.x) + if (currentSet) + _session->makeCurrent(); + + return imageIndex; + } + + // TODO: should not be necessary, but is for SteamVR 1.16.4 (but not 1.15.x) + if (currentSet) + _session->makeCurrent(); + + return -1; +} + +bool Swapchain::waitImage(XrDuration timeoutNs) const +{ + // Wait on the swapchain image + XrSwapchainImageWaitInfo waitInfo = { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO }; + waitInfo.timeout = timeoutNs; // 100ms + + bool currentSet = _session->checkCurrent(); + // GL context must not be bound in another thread + bool ret = check(xrWaitSwapchainImage(_swapchain, &waitInfo), + "Failed to wait for swapchain image"); + + // TODO: should not be necessary, but is for SteamVR 1.16.4 (but not 1.15.x) + if (currentSet) + _session->makeCurrent(); + + return ret; +} + +void Swapchain::releaseImage() const +{ + // Release the swapchain image + bool currentSet = _session->checkCurrent(); + // GL context must not be bound in another thread + check(xrReleaseSwapchainImage(_swapchain, nullptr), + "Failed to release OpenXR swapchain image"); + + // TODO: should not be necessary, but is for SteamVR 1.16.4 (but not 1.15.x) + if (currentSet) + _session->makeCurrent(); +} diff --git a/3rdparty/osgXR/src/OpenXR/Swapchain.h b/3rdparty/osgXR/src/OpenXR/Swapchain.h new file mode 100644 index 000000000..317b43761 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/Swapchain.h @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_SWAPCHAIN +#define OSGXR_OPENXR_SWAPCHAIN 1 + +#include "Session.h" +#include "System.h" + +#include <osg/Referenced> +#include <osg/Texture2D> +#include <osg/ref_ptr> + +#include <cinttypes> +#include <openxr/openxr.h> + +namespace osgXR { + +namespace OpenXR { + +class Swapchain : public osg::Referenced +{ + public: + + // GL context must not be bound in another thread + Swapchain(osg::ref_ptr<Session> session, + const System::ViewConfiguration::View &view, + XrSwapchainUsageFlags usageFlags, + int64_t format); + // GL context must not be bound in another thread + virtual ~Swapchain(); + + // Error checking + + inline bool valid() const + { + return _swapchain != XR_NULL_HANDLE; + } + + inline bool check(XrResult result, const char *warnMsg) const + { + return _session->check(result, warnMsg); + } + + // Conversions + + inline XrSession getXrSession() const + { + return _session->getXrSession(); + } + + inline XrSwapchain getXrSwapchain() const + { + return _swapchain; + } + + // Accessors + + inline uint32_t getWidth() const + { + return _width; + } + + inline uint32_t getHeight() const + { + return _height; + } + + inline uint32_t getSamples() const + { + return _samples; + } + + inline int64_t getFormat() const + { + return _format; + } + + // Queries + + typedef std::vector<GLuint> ImageTextures; + // GL context must not be bound in another thread + const ImageTextures &getImageTextures() const; + + osg::ref_ptr<osg::Texture2D> getImageOsgTexture(unsigned int index) const; + + // Operations + + // GL context must not be bound in another thread + int acquireImage() const; + // GL context must not be bound in another thread + bool waitImage(XrDuration timeoutNs) const; + // GL context must not be bound in another thread + void releaseImage() const; + + protected: + + // Session data + osg::ref_ptr<Session> _session; + XrSwapchain _swapchain; + uint32_t _width; + uint32_t _height; + uint32_t _samples; + int64_t _format; + + // Image OpenGL textures + mutable bool _readImageTextures; + mutable ImageTextures _imageTextures; + mutable std::vector<osg::ref_ptr<osg::Texture2D>> _imageOsgTextures; + + // Current image + mutable int _currentImage; +}; + +} // osgXR::OpenXR + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/SwapchainGroup.cpp b/3rdparty/osgXR/src/OpenXR/SwapchainGroup.cpp new file mode 100644 index 000000000..f24ac958e --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/SwapchainGroup.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "SwapchainGroup.h" + +#include <osg/Notify> + +using namespace osgXR::OpenXR; + +SwapchainGroup::SwapchainGroup(osg::ref_ptr<Session> session, + const System::ViewConfiguration::View &view, + XrSwapchainUsageFlags usageFlags, + int64_t format, + XrSwapchainUsageFlags depthUsageFlags, + int64_t depthFormat) : + _swapchain(new Swapchain(session, view, usageFlags, format)) +{ + if (depthFormat) + _depthSwapchain = new Swapchain(session, view, depthUsageFlags, depthFormat); +} + +SwapchainGroup::~SwapchainGroup() +{ +} + +int SwapchainGroup::acquireImages() const +{ + int imageIndex = _swapchain->acquireImage(); + if (depthValid()) + { + int depthImageIndex = _depthSwapchain->acquireImage(); + if (imageIndex != depthImageIndex) + OSG_WARN << "Depth swapchain image mismatch, expected " << imageIndex + << ", got " << depthImageIndex << std::endl; + } + return imageIndex; +} + +bool SwapchainGroup::waitImages(XrDuration timeoutNs) const +{ + bool ret = _swapchain->waitImage(timeoutNs); + if (depthValid()) + { + bool depthRet = _depthSwapchain->waitImage(timeoutNs); + ret = ret && depthRet; + } + return ret; +} + +void SwapchainGroup::releaseImages() const +{ + _swapchain->releaseImage(); + if (depthValid()) + _depthSwapchain->releaseImage(); +} diff --git a/3rdparty/osgXR/src/OpenXR/SwapchainGroup.h b/3rdparty/osgXR/src/OpenXR/SwapchainGroup.h new file mode 100644 index 000000000..93f637f28 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/SwapchainGroup.h @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_SWAPCHAIN_GROUP +#define OSGXR_OPENXR_SWAPCHAIN_GROUP 1 + +#include "Swapchain.h" + +#include <osg/Referenced> +#include <osg/ref_ptr> + +namespace osgXR { + +namespace OpenXR { + +class SwapchainGroupSubImage; + +/// Groups colour and depth swapchains together +class SwapchainGroup : public osg::Referenced +{ + public: + + typedef SwapchainGroupSubImage SubImage; + + // GL context must not be bound in another thread + SwapchainGroup(osg::ref_ptr<Session> session, + const System::ViewConfiguration::View &view, + XrSwapchainUsageFlags usageFlags, + int64_t format, + XrSwapchainUsageFlags depthUsageFlags = 0, + int64_t depthFormat = 0); + // GL context must not be bound in another thread + virtual ~SwapchainGroup(); + + // Error checking + + inline bool valid() const + { + return _swapchain->valid(); + } + + inline bool depthValid() const + { + return _depthSwapchain.valid() && _depthSwapchain->valid(); + } + + // Accessors + + inline osg::ref_ptr<Swapchain> getSwapchain() const + { + return _swapchain; + } + + inline osg::ref_ptr<Swapchain> getDepthSwapchain() const + { + return _depthSwapchain; + } + + inline XrSwapchain getXrSwapchain() const + { + return _swapchain->getXrSwapchain(); + } + + inline XrSwapchain getDepthXrSwapchain() const + { + if (_depthSwapchain.valid()) + return _depthSwapchain->getXrSwapchain(); + else + return XR_NULL_HANDLE; + } + + inline uint32_t getWidth() const + { + return _swapchain->getWidth(); + } + + inline uint32_t getHeight() const + { + return _swapchain->getHeight(); + } + + inline uint32_t getSamples() const + { + return _swapchain->getSamples(); + } + + // Queries + + typedef Swapchain::ImageTextures ImageTextures; + const ImageTextures &getImageTextures() const + { + return _swapchain->getImageTextures(); + } + const ImageTextures &getDepthImageTextures() const + { + return _depthSwapchain->getImageTextures(); + } + + // Operations + + int acquireImages() const; + bool waitImages(XrDuration timeoutNs) const; + void releaseImages() const; + + protected: + + osg::ref_ptr<Swapchain> _swapchain; + osg::ref_ptr<Swapchain> _depthSwapchain; +}; + +} + +} + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/SwapchainGroupSubImage.h b/3rdparty/osgXR/src/OpenXR/SwapchainGroupSubImage.h new file mode 100644 index 000000000..1ba66e16d --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/SwapchainGroupSubImage.h @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_SWAPCHAIN_GROUP_SUB_IMAGE +#define OSGXR_OPENXR_SWAPCHAIN_GROUP_SUB_IMAGE 1 + +#include "SwapchainGroup.h" + +#include <osg/ref_ptr> + +#include <openxr/openxr.h> + +namespace osgXR { + +namespace OpenXR { + +class SwapchainGroupSubImage +{ + public: + SwapchainGroupSubImage(SwapchainGroup *group) : + _group(group), + _x(0), + _y(0), + _width(group->getWidth()), + _height(group->getHeight()), + _arrayIndex(0) + { + } + + SwapchainGroupSubImage(SwapchainGroup *group, + const System::ViewConfiguration::View::Viewport &vp) : + _group(group), + _x(vp.x), + _y(vp.y), + _width(vp.width), + _height(vp.height), + _arrayIndex(vp.arrayIndex) + { + } + + bool valid() const + { + return _group->valid(); + } + + bool depthValid() const + { + return _group->depthValid(); + } + + osg::ref_ptr<SwapchainGroup> getSwapchainGroup() const + { + return _group; + } + + uint32_t getX() const + { + return _x; + } + + uint32_t getY() const + { + return _y; + } + + uint32_t getWidth() const + { + return _width; + } + + uint32_t getHeight() const + { + return _height; + } + + uint32_t getArrayIndex() const + { + return _arrayIndex; + } + + void getXrSubImage(XrSwapchainSubImage *out) const + { + out->swapchain = _group->getXrSwapchain(); + out->imageRect.offset = { (int32_t)_x, + (int32_t)_y }; + out->imageRect.extent = { (int32_t)_width, + (int32_t)_height }; + out->imageArrayIndex = _arrayIndex; + } + + void getDepthXrSubImage(XrSwapchainSubImage *out) const + { + out->swapchain = _group->getDepthXrSwapchain(); + out->imageRect.offset = { (int32_t)_x, + (int32_t)_y }; + out->imageRect.extent = { (int32_t)_width, + (int32_t)_height }; + out->imageArrayIndex = _arrayIndex; + } + + protected: + osg::ref_ptr<SwapchainGroup> _group; + uint32_t _x; + uint32_t _y; + uint32_t _width; + uint32_t _height; + uint32_t _arrayIndex; +}; + +} + +} + +#endif diff --git a/3rdparty/osgXR/src/OpenXR/System.cpp b/3rdparty/osgXR/src/OpenXR/System.cpp new file mode 100644 index 000000000..e19e4ee59 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/System.cpp @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "System.h" + +#include <cstring> + +using namespace osgXR::OpenXR; + +void System::getProperties() const +{ + XrSystemProperties properties; + properties.type = XR_TYPE_SYSTEM_PROPERTIES; + properties.next = nullptr; + + if (check(xrGetSystemProperties(getXrInstance(), _systemId, &properties), + "Failed to get OpenXR system properties")) + { + memcpy(_systemName, properties.systemName, sizeof(_systemName)); + _orientationTracking = properties.trackingProperties.orientationTracking; + _positionTracking = properties.trackingProperties.positionTracking; + } + + _readProperties = true; +} + +const System::ViewConfiguration::Views &System::ViewConfiguration::getViews() const +{ + if (!_readViews) + { + uint32_t viewCount = 0; + if (check(xrEnumerateViewConfigurationViews(_system->getXrInstance(), + _system->getXrSystemId(), + _type, + 0, &viewCount, nullptr), + "Failed to count OpenXR view configuration views")) + { + if (viewCount) + { + std::vector<XrViewConfigurationView> views(viewCount, + { XR_TYPE_VIEW_CONFIGURATION_VIEW }); + if (check(xrEnumerateViewConfigurationViews(_system->getXrInstance(), + _system->getXrSystemId(), + _type, + views.size(), &viewCount, + views.data()), + "Failed to enumerate OpenXR view configuration views")) + { + for (auto &view: views) + _views.push_back(View(view)); + } + } + } + + _readViews = true; + } + + return _views; +} + +const System::ViewConfiguration::EnvBlendModes &System::ViewConfiguration::getEnvBlendModes() const +{ + if (!_readEnvBlendModes) + { + uint32_t blendModeCount = 0; + if (check(xrEnumerateEnvironmentBlendModes(_system->getXrInstance(), + _system->getXrSystemId(), + _type, + 0, &blendModeCount, nullptr), + "Failed to count OpenXR environment blend modes")) + { + if (blendModeCount) + { + _envBlendModes.resize(blendModeCount); + if (!check(xrEnumerateEnvironmentBlendModes(_system->getXrInstance(), + _system->getXrSystemId(), + _type, + _envBlendModes.size(), + &blendModeCount, + _envBlendModes.data()), + "Failed to enumerate OpenXR environment blend modes")) + { + _envBlendModes.resize(0); + } + } + } + + _readEnvBlendModes = true; + } + + return _envBlendModes; +} + +const System::ViewConfigurations &System::getViewConfigurations() const +{ + if (!_readViewConfigurations) + { + uint32_t viewConfigCount = 0; + if (check(xrEnumerateViewConfigurations(getXrInstance(), getXrSystemId(), + 0, &viewConfigCount, nullptr), + "Failed to count OpenXR view configuration types")) + { + if (viewConfigCount) + { + std::vector<XrViewConfigurationType> types(viewConfigCount); + if (check(xrEnumerateViewConfigurations(getXrInstance(), getXrSystemId(), + types.size(), &viewConfigCount, + types.data()), + "Failed to enumerate OpenXR view configuration types")) + { + _viewConfigurations.reserve(viewConfigCount); + for (auto type: types) + _viewConfigurations.push_back(ViewConfiguration(this, type)); + } + } + } + + _readViewConfigurations = true; + } + + return _viewConfigurations; +} diff --git a/3rdparty/osgXR/src/OpenXR/System.h b/3rdparty/osgXR/src/OpenXR/System.h new file mode 100644 index 000000000..bfc412731 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXR/System.h @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_OPENXR_SYSTEM +#define OSGXR_OPENXR_SYSTEM 1 + +#include "Instance.h" + +#include <osg/DisplaySettings> +#include <osg/ref_ptr> + +#include <algorithm> +#include <vector> + +namespace osgXR { + +namespace OpenXR { + +class System +{ + public: + + System(Instance *instance, XrSystemId systemId) : + _instance(instance), + _systemId(systemId), + _readProperties(false), + _orientationTracking(false), + _positionTracking(false), + _readViewConfigurations(false) + { + } + + // Error checking + + inline bool check(XrResult result, const char *warnMsg) const + { + return _instance->check(result, warnMsg); + } + + // Conversions + + inline Instance *getInstance() + { + return _instance; + } + inline const Instance *getInstance() const + { + return _instance; + } + + inline XrInstance getXrInstance() const + { + return _instance->getXrInstance(); + } + + inline XrSystemId getXrSystemId() const + { + return _systemId; + } + + // Queries + + void getProperties() const; + + inline const char *getSystemName() const + { + if (!_readProperties) + getProperties(); + return _systemName; + } + + inline bool getOrientationTracking() const + { + if (!_readProperties) + getProperties(); + return _orientationTracking; + } + + inline bool getPositionTracking() const + { + if (!_readProperties) + getProperties(); + return _positionTracking; + } + + class ViewConfiguration + { + + public: + + ViewConfiguration(const System *system, XrViewConfigurationType type) : + _system(system), + _type(type), + _readViews(false), + _readEnvBlendModes(false) + { + } + + XrViewConfigurationType getType() const + { + return _type; + } + + // Queries + + class View + { + + public: + + struct Viewport + { + uint32_t x, y, width, height, arrayIndex; + }; + + View(uint32_t recommendedWidth, + uint32_t recommendedHeight, + uint32_t recommendedSamples = 1) : + _recommendedWidth(recommendedWidth), + _recommendedHeight(recommendedHeight), + _recommendedSamples(recommendedSamples) + { + } + + View(const XrViewConfigurationView &view) : + _recommendedWidth(view.recommendedImageRectWidth), + _recommendedHeight(view.recommendedImageRectHeight), + _recommendedSamples(view.recommendedSwapchainSampleCount) + { + } + + uint32_t getRecommendedWidth() const + { + return _recommendedWidth; + } + + uint32_t getRecommendedHeight() const + { + return _recommendedHeight; + } + + + uint32_t getRecommendedSamples() const + { + return _recommendedSamples; + } + + /// Tile another view horizontally after this one + struct Viewport tileHorizontally(const View &other) + { + struct Viewport vp; + vp.x = _recommendedWidth; + vp.y = 0; + vp.width = other._recommendedWidth; + vp.height = other._recommendedHeight; + vp.arrayIndex = 0; + + _recommendedWidth += other._recommendedWidth; + _recommendedHeight = std::max(_recommendedHeight, + other._recommendedHeight); + return vp; + } + + protected: + + uint32_t _recommendedWidth; + uint32_t _recommendedHeight; + uint32_t _recommendedSamples; + }; + + typedef std::vector<View> Views; + const Views &getViews() const; + + typedef std::vector<XrEnvironmentBlendMode> EnvBlendModes; + const EnvBlendModes &getEnvBlendModes() const; + + protected: + + inline bool check(XrResult result, const char *warnMsg) const + { + return _system->getInstance()->check(result, warnMsg); + } + + const System *_system; + XrViewConfigurationType _type; + + // Views + mutable bool _readViews; + mutable Views _views; + + // Environment blend modes + mutable bool _readEnvBlendModes; + mutable EnvBlendModes _envBlendModes; + }; + + typedef std::vector<ViewConfiguration> ViewConfigurations; + const ViewConfigurations &getViewConfigurations() const; + + protected: + + // System data + Instance *_instance; + XrSystemId _systemId; + + // Properties + mutable char _systemName[XR_MAX_SYSTEM_NAME_SIZE]; + mutable bool _readProperties; + mutable bool _orientationTracking; + mutable bool _positionTracking; + + // View configurations + mutable bool _readViewConfigurations; + mutable ViewConfigurations _viewConfigurations; + +}; + +} // osgXR::OpenXR + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/OpenXRDisplay.cpp b/3rdparty/osgXR/src/OpenXRDisplay.cpp new file mode 100644 index 000000000..63cbfa892 --- /dev/null +++ b/3rdparty/osgXR/src/OpenXRDisplay.cpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include <osgXR/OpenXRDisplay> + +#include <osg/ref_ptr> +#include <osgViewer/ViewerBase> + +#include "XRState.h" +#include "XRRealizeOperation.h" + +using namespace osgXR; + +OpenXRDisplay::OpenXRDisplay() +{ +} + +OpenXRDisplay::OpenXRDisplay(Settings *settings): + _settings(settings) +{ +} + +OpenXRDisplay::OpenXRDisplay(const OpenXRDisplay& rhs, + const osg::CopyOp& copyop): + ViewConfig(rhs,copyop), + _settings(rhs._settings) +{ +} + +OpenXRDisplay::~OpenXRDisplay() +{ +} + +void OpenXRDisplay::configure(osgViewer::View &view) const +{ + osgViewer::ViewerBase *viewer = dynamic_cast<osgViewer::ViewerBase *>(&view); + if (!viewer) + return; + + _state = new XRState(_settings); + viewer->setRealizeOperation(new XRRealizeOperation(_state, &view)); +} diff --git a/3rdparty/osgXR/src/Settings.cpp b/3rdparty/osgXR/src/Settings.cpp new file mode 100644 index 000000000..5b56096d9 --- /dev/null +++ b/3rdparty/osgXR/src/Settings.cpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include <osgXR/Settings> + +#include <openxr/openxr.h> + +#include "OpenXR/Instance.h" + +using namespace osgXR; + +Settings::Settings() : + _appName("osgXR"), + _appVersion(1), + _validationLayer(false), + _depthInfo(false), + _visibilityMask(true), + _formFactor(HEAD_MOUNTED_DISPLAY), + _preferredEnvBlendModeMask(0), + _allowedEnvBlendModeMask(0), + _vrMode(VRMODE_AUTOMATIC), + _swapchainMode(SWAPCHAIN_AUTOMATIC), + _unitsPerMeter(1.0f) +{ +} + +Settings::~Settings() +{ +} + +Settings *Settings::instance() +{ + static osg::ref_ptr<Settings> settings = new Settings(); + return settings; +} + +unsigned int Settings::_diff(const Settings &other) const +{ + unsigned int ret = DIFF_NONE; + if (_appName != other._appName || + _appVersion != other._appVersion) + ret |= DIFF_APP_INFO; + if (_validationLayer != other._validationLayer) + ret |= DIFF_VALIDATION_LAYER; + if (_depthInfo != other._depthInfo) + ret |= DIFF_DEPTH_INFO; + if (_visibilityMask != other._visibilityMask) + ret |= DIFF_VISIBILITY_MASK; + if (_formFactor != other._formFactor) + ret |= DIFF_FORM_FACTOR; + if (_preferredEnvBlendModeMask != other._preferredEnvBlendModeMask || + _allowedEnvBlendModeMask != other._allowedEnvBlendModeMask) + ret |= DIFF_BLEND_MODE; + if (_vrMode != other._vrMode) + ret |= DIFF_VR_MODE; + if (_swapchainMode != other._swapchainMode) + ret |= DIFF_SWAPCHAIN_MODE; + if (_mirrorSettings != other._mirrorSettings) + ret |= DIFF_MIRROR; + if (_unitsPerMeter != other._unitsPerMeter) + ret |= DIFF_SCALE; + return ret; +} diff --git a/3rdparty/osgXR/src/Subaction.cpp b/3rdparty/osgXR/src/Subaction.cpp new file mode 100644 index 000000000..6584335dd --- /dev/null +++ b/3rdparty/osgXR/src/Subaction.cpp @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "Subaction.h" +#include "XRState.h" + +#include <osgXR/Manager> + +using namespace osgXR; + +// Internal API + +Subaction::Private::Private(XRState *state, + const std::string &path) : + _state(state), + _pathString(path) +{ +} + +void Subaction::Private::registerPublic(Subaction *subaction) +{ + _publics.insert(subaction); +} + +void Subaction::Private::unregisterPublic(Subaction *subaction) +{ + _publics.erase(subaction); +} + +InteractionProfile *Subaction::Private::getCurrentProfile() +{ + if (!_currentProfile.valid()) + { + if (_path.valid()) + _currentProfile = _state->getCurrentInteractionProfile(_path); + } + + return _currentProfile.get(); +} + +void Subaction::Private::onInteractionProfileChanged(OpenXR::Session *session) +{ + // Ensure path is set up + setup(session->getInstance()); + + // Find whether this subaction's current interaction profile has changed + InteractionProfile *prevProfile = _currentProfile.get(); + _currentProfile = nullptr; + InteractionProfile *newProfile = getCurrentProfile(); + if (newProfile != prevProfile) + { + // Notify any derived Subaction classes from the app + for (auto *pub: _publics) + pub->onProfileChanged(newProfile); + } +} + +const OpenXR::Path &Subaction::Private::setup(OpenXR::Instance *instance) +{ + if (!_path.valid()) + _path = OpenXR::Path(instance, _pathString); + return _path; +} + +void Subaction::Private::cleanupSession() +{ + bool hadProfile = _currentProfile.valid(); + _currentProfile = nullptr; + if (hadProfile) + { + // Notify any derived Subaction classes from the app + for (auto *pub: _publics) + pub->onProfileChanged(nullptr); + } +} + +void Subaction::Private::cleanupInstance() +{ + _path = OpenXR::Path(); +} + +// Public API + +Subaction::Subaction(Manager *manager, + const std::string &path) : + _private(manager->_getXrState()->getSubaction(path)) +{ + _private->registerPublic(this); +} + +Subaction::~Subaction() +{ + _private->unregisterPublic(this); +} + +const std::string &Subaction::getPath() const +{ + return _private->getPathString(); +} + +InteractionProfile *Subaction::getCurrentProfile() +{ + return _private->getCurrentProfile(); +} + +void Subaction::onProfileChanged(InteractionProfile *newProfile) +{ + // This is for derived classes to implement to their own ends +} diff --git a/3rdparty/osgXR/src/Subaction.h b/3rdparty/osgXR/src/Subaction.h new file mode 100644 index 000000000..272227dd4 --- /dev/null +++ b/3rdparty/osgXR/src/Subaction.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_SUBACTION +#define OSGXR_SUBACTION 1 + +#include <osgXR/Subaction> + +#include "OpenXR/Path.h" + +#include <set> +#include <string> + +namespace osgXR { + +class XRState; + +namespace OpenXR { + class Instance; +}; + +class Subaction::Private +{ + public: + + static std::shared_ptr<Private> get(Subaction *pub) + { + if (pub) + return pub->_private; + else + return nullptr; + } + + Private(XRState *state, const std::string &path); + + // Public object registration + void registerPublic(Subaction *subaction); + void unregisterPublic(Subaction *subaction); + + // Accessors + + /// Get the subaction's path as a string. + const std::string &getPathString() const + { + return _pathString; + } + + /// Find the current interaction profile. + InteractionProfile *getCurrentProfile(); + + // Events + + /// Notify that an interaction profile has changed. + void onInteractionProfileChanged(OpenXR::Session *session); + + /// Setup path with an OpenXR instance. + const OpenXR::Path &setup(OpenXR::Instance *instance); + /// Clean up current profile before an OpenXR session is destroyed. + void cleanupSession(); + /// Clean up path before an OpenXR instance is destroyed. + void cleanupInstance(); + + private: + + XRState *_state; + std::string _pathString; + std::set<Subaction *> _publics; + + OpenXR::Path _path; + osg::ref_ptr<InteractionProfile> _currentProfile; +}; + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/Version.h.in b/3rdparty/osgXR/src/Version.h.in new file mode 100644 index 000000000..7b77d8d54 --- /dev/null +++ b/3rdparty/osgXR/src/Version.h.in @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_Version +#define OSGXR_Version 1 + +#define OSGXR_MAJOR_VERSION @osgXR_MAJOR_VERSION@ +#define OSGXR_MINOR_VERSION @osgXR_MINOR_VERSION@ +#define OSGXR_PATCH_VERSION @osgXR_PATCH_VERSION@ +#define OSGXR_SOVERSION @osgXR_SOVERSION@ + +#endif diff --git a/3rdparty/osgXR/src/View.cpp b/3rdparty/osgXR/src/View.cpp new file mode 100644 index 000000000..fe8009d23 --- /dev/null +++ b/3rdparty/osgXR/src/View.cpp @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include <osgXR/View> + +using namespace osgXR; + +View::View(osgViewer::GraphicsWindow *window, osgViewer::View *osgView) : + _window(window), + _osgView(osgView) +{ +} + +View::~View() +{ +} diff --git a/3rdparty/osgXR/src/XRFramebuffer.cpp b/3rdparty/osgXR/src/XRFramebuffer.cpp new file mode 100644 index 000000000..e8cce0819 --- /dev/null +++ b/3rdparty/osgXR/src/XRFramebuffer.cpp @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "XRFramebuffer.h" + +#include <osg/FrameBufferObject> +#include <osg/Image> +#include <osg/State> +#include <osg/Version> + +using namespace osgXR; + +#if(OSG_VERSION_GREATER_OR_EQUAL(3, 4, 0)) +typedef osg::GLExtensions OSG_GLExtensions; +#else +typedef osg::FBOExtensions OSG_GLExtensions; +#endif + +static const OSG_GLExtensions* getGLExtensions(const osg::State& state) +{ +#if(OSG_VERSION_GREATER_OR_EQUAL(3, 4, 0)) + return state.get<osg::GLExtensions>(); +#else + return osg::FBOExtensions::instance(state.getContextID(), true); +#endif +} + +XRFramebuffer::XRFramebuffer(uint32_t width, uint32_t height, + GLuint texture, GLuint depthTexture) : + _width(width), + _height(height), + _depthFormat(GL_DEPTH_COMPONENT16), + _fbo(0), + _texture(texture), + _depthTexture(depthTexture), + _generated(false), + _boundTexture(false), + _boundDepthTexture(false), + _deleteDepthTexture(false) +{ +} + +XRFramebuffer::~XRFramebuffer() +{ +} + +bool XRFramebuffer::valid(osg::State &state) const +{ + if (!_fbo) + return false; + + const OSG_GLExtensions *fbo_ext = getGLExtensions(state); + GLenum complete = fbo_ext->glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT); + switch (complete) + { + case GL_FRAMEBUFFER_COMPLETE_EXT: + return true; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: + OSG_WARN << "FBO Incomplete attachment" << std::endl; + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: + OSG_WARN << "FBO Incomplete missing attachment" << std::endl; + break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: + OSG_WARN << "FBO Incomplete draw buffer" << std::endl; + break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: + OSG_WARN << "FBO Incomplete read buffer" << std::endl; + break; + case GL_FRAMEBUFFER_UNSUPPORTED_EXT: + OSG_WARN << "FBO Incomplete unsupported" << std::endl; + break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT: + OSG_WARN << "FBO Incomplete multisample" << std::endl; + break; + default: + OSG_WARN << "FBO Incomplete ??? (0x" << std::hex << complete << std::dec << ")" << std::endl; + break; + } + return false; +} + +void XRFramebuffer::bind(osg::State &state) +{ + const OSG_GLExtensions *fbo_ext = getGLExtensions(state); + + if (!_fbo && !_generated) + { + fbo_ext->glGenFramebuffers(1, &_fbo); + _generated = true; + } + + if (_fbo) + { + fbo_ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, _fbo); + if (!_boundTexture && _texture) + { + fbo_ext->glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, _texture, 0); + _boundTexture = true; + } + if (!_boundDepthTexture) + { + if (!_depthTexture) + { + glGenTextures(1, &_depthTexture); + glBindTexture(GL_TEXTURE_2D, _depthTexture); + glTexImage2D(GL_TEXTURE_2D, 0, _depthFormat, _width, _height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, nullptr); + glBindTexture(GL_TEXTURE_2D, 0); + + _deleteDepthTexture = true; + } + + fbo_ext->glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, _depthTexture, 0); + _boundDepthTexture = true; + + valid(state); + } + } +} + +void XRFramebuffer::unbind(osg::State &state) +{ + const OSG_GLExtensions *fbo_ext = getGLExtensions(state); + + if (_fbo && _generated) + fbo_ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0); +} + +void XRFramebuffer::releaseGLObjects(osg::State &state) +{ + // FIXME can we do it like RenderBuffer::releaseGLObjects? + // FIXME better yet, switch to use FrameBufferObject, dynamically bound + + // GL context must be current + if (_fbo) + { + /* + unsigned int contextID = state->getContextID(); + osg::get<GLFrameBufferObjectManager>(contextID)->scheduleGLObjectForDeletion(_fbo); + */ + const OSG_GLExtensions *fbo_ext = getGLExtensions(state); + fbo_ext->glDeleteFramebuffers(1, &_fbo); + _fbo = 0; + } + if (_deleteDepthTexture) + { + glDeleteTextures(1, &_depthTexture); + _depthTexture = 0; + _deleteDepthTexture = false; + } +} diff --git a/3rdparty/osgXR/src/XRFramebuffer.h b/3rdparty/osgXR/src/XRFramebuffer.h new file mode 100644 index 000000000..6f04a93d1 --- /dev/null +++ b/3rdparty/osgXR/src/XRFramebuffer.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_XRFRAMEBUFFER +#define OSGXR_XRFRAMEBUFFER 1 + +#include <osg/GL> +#include <osg/Referenced> + +#include <cinttypes> + +namespace osgXR { + +class XRFramebuffer : public osg::Referenced +{ + public: + + explicit XRFramebuffer(uint32_t width, uint32_t height, + GLuint texture, GLuint depthTexture = 0); + // releaseGLObjects() first + virtual ~XRFramebuffer(); + + void setDepthFormat(GLenum depthFormat) + { + _depthFormat = depthFormat; + } + + bool valid(osg::State &state) const; + void bind(osg::State &state); + void unbind(osg::State &state); + // GL context must be current + void releaseGLObjects(osg::State &state); + + protected: + + uint32_t _width; + uint32_t _height; + GLenum _depthFormat; + + GLuint _fbo; + GLuint _texture; + GLuint _depthTexture; + + bool _generated; + bool _boundTexture; + bool _boundDepthTexture; + bool _deleteDepthTexture; +}; + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/XRRealizeOperation.cpp b/3rdparty/osgXR/src/XRRealizeOperation.cpp new file mode 100644 index 000000000..ae12d9477 --- /dev/null +++ b/3rdparty/osgXR/src/XRRealizeOperation.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "XRRealizeOperation.h" + +#include "XRState.h" + +#include <osgViewer/GraphicsWindow> + +using namespace osgXR; + +XRRealizeOperation::XRRealizeOperation(osg::ref_ptr<XRState> state, + osgViewer::View *view) : + osg::GraphicsOperation("XRRealizeOperation", false), + _state(state), + _view(view), + _realized(false) +{ +} + +void XRRealizeOperation::operator () (osg::GraphicsContext *gc) +{ + if (!_realized) + { + OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex); + gc->makeCurrent(); + + auto *window = dynamic_cast<osgViewer::GraphicsWindow *>(gc); + if (window) + { + _state->init(window, _view); + _realized = true; + } + } +} diff --git a/3rdparty/osgXR/src/XRRealizeOperation.h b/3rdparty/osgXR/src/XRRealizeOperation.h new file mode 100644 index 000000000..ec4954fa2 --- /dev/null +++ b/3rdparty/osgXR/src/XRRealizeOperation.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_XRREALIZEOPERATION +#define OSGXR_XRREALIZEOPERATION 1 + +#include <osg/ref_ptr> +#include <osgViewer/View> + +namespace osgXR { + +class XRState; + +class XRRealizeOperation : public osg::GraphicsOperation +{ + public: + + explicit XRRealizeOperation(osg::ref_ptr<XRState> state, + osgViewer::View *view); + + void operator () (osg::GraphicsContext *gc) override; + + bool realized() const + { + return _realized; + } + + protected: + + OpenThreads::Mutex _mutex; + osg::ref_ptr<XRState> _state; + osgViewer::View *_view; + bool _realized; +}; + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/XRState.cpp b/3rdparty/osgXR/src/XRState.cpp new file mode 100644 index 000000000..5beda9e5a --- /dev/null +++ b/3rdparty/osgXR/src/XRState.cpp @@ -0,0 +1,1589 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include "XRState.h" +#include "XRStateCallbacks.h" +#include "ActionSet.h" +#include "InteractionProfile.h" +#include "Subaction.h" +#include "projection.h" + +#include <osgXR/Manager> + +#include <osg/Camera> +#include <osg/ColorMask> +#include <osg/Depth> +#include <osg/DisplaySettings> +#include <osg/FrameBufferObject> +#include <osg/Notify> +#include <osg/MatrixTransform> +#include <osg/RenderInfo> +#include <osg/View> + +#include <osgUtil/SceneView> + +#include <osgViewer/GraphicsWindow> +#include <osgViewer/Renderer> +#include <osgViewer/View> + +#include <cassert> +#include <climits> +#include <cmath> +#include <sstream> + +using namespace osgXR; + +XRState::XRState(Settings *settings, Manager *manager) : + _settings(settings), + _settingsCopy(*settings), + _manager(manager), + _visibilityMaskLeft(0), + _visibilityMaskRight(0), + _actionsUpdated(false), + _currentState(VRSTATE_DISABLED), + _downState(VRSTATE_MAX), + _upState(VRSTATE_DISABLED), + _upDelay(0), + _probing(false), + _stateChanged(false), + _probed(false), + _useDepthInfo(false), + _useVisibilityMask(false), + _formFactor(XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY), + _system(nullptr), + _chosenViewConfig(nullptr), + _chosenEnvBlendMode(XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM), + _vrMode(VRMode::VRMODE_AUTOMATIC), + _swapchainMode(SwapchainMode::SWAPCHAIN_AUTOMATIC) +{ +} + +XRState::XRSwapchain::XRSwapchain(XRState *state, + osg::ref_ptr<OpenXR::Session> session, + const OpenXR::System::ViewConfiguration::View &view, + int64_t chosenSwapchainFormat, + int64_t chosenDepthSwapchainFormat) : + OpenXR::SwapchainGroup(session, view, + XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, + chosenSwapchainFormat, + XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + chosenDepthSwapchainFormat), + _state(state), + _numDrawPasses(0), + _drawPassesDone(0), + _imagesReady(false) +{ + if (valid()) + { + // Create framebuffer objects for each image in swapchain + auto &textures = getImageTextures(); + const ImageTextures *depthTextures = nullptr; + if (depthValid()) + { + depthTextures = &getDepthImageTextures(); + if (textures.size() != depthTextures->size()) + OSG_WARN << "Depth swapchain image count mismatch, expected " << textures.size() << ", got " << depthTextures->size() << std::endl; + } + + _imageFramebuffers.reserve(textures.size()); + for (unsigned int i = 0; i < textures.size(); ++i) + { + GLuint texture = textures[i]; + GLuint depthTexture = 0; + if (depthTextures) + depthTexture = (*depthTextures)[i]; + XRFramebuffer *fb = new XRFramebuffer(getWidth(), + getHeight(), + texture, depthTexture); + _imageFramebuffers.push_back(fb); + } + } +} + +XRState::XRSwapchain::~XRSwapchain() +{ + osg::State *state = _state->_window->getState(); + // FIXME window has no state on shutdown... + if (!state) + return; + // Explicitly release FBOs etc + // GL context must be current + for (unsigned int i = 0; i < _imageFramebuffers.size(); ++i) + _imageFramebuffers[i]->releaseGLObjects(*state); +} + +void XRState::XRSwapchain::setupImage(const osg::FrameStamp *stamp) +{ + auto opt_fbo = _imageFramebuffers[stamp]; + bool firstPass = !opt_fbo.has_value(); + int imageIndex; + if (firstPass) + { + // Acquire a swapchain image + imageIndex = acquireImages(); + if (imageIndex < 0 || (unsigned int)imageIndex >= _imageFramebuffers.size()) + { + OSG_WARN << "XRView::preDrawCallback(): Failure to acquire OpenXR swapchain image (got image index " << imageIndex << ")" << std::endl; + return; + } + _imageFramebuffers.setStamp(imageIndex, stamp); + opt_fbo.emplace(_imageFramebuffers[imageIndex]); + _drawPassesDone = 0; + // Images aren't ready until we've waited for them to be so + _imagesReady = false; + } +} + +void XRState::XRSwapchain::preDrawCallback(osg::RenderInfo &renderInfo) +{ + const osg::FrameStamp *stamp = renderInfo.getState()->getFrameStamp(); + setupImage(stamp); + + auto opt_fbo = _imageFramebuffers[stamp]; + if (!opt_fbo.has_value()) + return; + + const auto &fbo = opt_fbo.value(); + + // Bind the framebuffer + osg::State &state = *renderInfo.getState(); + fbo->bind(state); + + if (!_imagesReady) + { + // Wait for the image to be ready to render into + if (!waitImages(100e6 /* 100ms */)) + { + OSG_WARN << "XRView::preDrawCallback(): Failure to wait for OpenXR swapchain image" << std::endl; + + // Unclear what the best course of action is here... + fbo->unbind(state); + return; + } + + _imagesReady = true; + } +} + +void XRState::XRSwapchain::postDrawCallback(osg::RenderInfo &renderInfo) +{ + const osg::FrameStamp *stamp = renderInfo.getState()->getFrameStamp(); + auto opt_fbo = _imageFramebuffers[stamp]; + if (!opt_fbo.has_value()) + return; + const auto &fbo = opt_fbo.value(); + + // Unbind the framebuffer + osg::State& state = *renderInfo.getState(); + fbo->unbind(state); + + if (++_drawPassesDone == _numDrawPasses && _imagesReady) + { + // Done rendering. release the swapchain image + releaseImages(); + + _imagesReady = false; + } +} + +void XRState::XRSwapchain::endFrame() +{ + // Double check images are released + if (_imagesReady) + { + releaseImages(); + _imagesReady = false; + } +} + +osg::ref_ptr<osg::Texture2D> XRState::XRSwapchain::getOsgTexture(const osg::FrameStamp *stamp) +{ + int index = _imageFramebuffers.findStamp(stamp); + if (index < 0) + return nullptr; + return getSwapchain()->getImageOsgTexture(index); +} + +XRState::XRView::XRView(XRState *state, + uint32_t viewIndex, + osg::ref_ptr<XRSwapchain> swapchain) : + _state(state), + _swapchainSubImage(swapchain), + _viewIndex(viewIndex) +{ +} + +XRState::XRView::XRView(XRState *state, + uint32_t viewIndex, + osg::ref_ptr<XRSwapchain> swapchain, + const OpenXR::System::ViewConfiguration::View::Viewport &viewport) : + _state(state), + _swapchainSubImage(swapchain, viewport), + _viewIndex(viewIndex) +{ +} + +XRState::XRView::~XRView() +{ +} + +void XRState::XRView::setupCamera(osg::ref_ptr<osg::Camera> camera) +{ + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + // FIXME necessary I expect... + //camera->setRenderOrder(osg::Camera::PRE_RENDER, eye); + //camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); + camera->setAllowEventFocus(false); + camera->setReferenceFrame(osg::Camera::RELATIVE_RF); + //camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + //camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); + camera->setViewport(_swapchainSubImage.getX(), + _swapchainSubImage.getY(), + _swapchainSubImage.getWidth(), + _swapchainSubImage.getHeight()); + + // Here we avoid doing anything regarding OSG camera RTT attachment. + // Ideally we would use automatic methods within OSG for handling RTT but in this + // case it seemed simpler to handle FBO creation and selection within this class. + + // This initial draw callback is used to disable normal OSG camera setup which + // would undo our RTT FBO configuration. + camera->setInitialDrawCallback(new InitialDrawCallback(_state)); + + camera->setPreDrawCallback(new PreDrawCallback(getSwapchain())); + camera->setFinalDrawCallback(new PostDrawCallback(getSwapchain())); +} + +void XRState::XRView::endFrame(OpenXR::Session::Frame *frame) +{ + // Double check images are released + getSwapchain()->endFrame(); + + // Add view info to projection layer for compositor + osg::ref_ptr<OpenXR::CompositionLayerProjection> proj = _state->getProjectionLayer(); + if (proj != nullptr) + { + proj->addView(frame, _viewIndex, _swapchainSubImage, + _state->_useDepthInfo ? &_state->_depthInfo : nullptr); + } + else + { + OSG_WARN << "No projection layer" << std::endl; + } +} + +XRState::AppView::AppView(XRState *state, + osgViewer::GraphicsWindow *window, + osgViewer::View *osgView) : + View(window, osgView), + _valid(false), + _state(state) +{ +} + +void XRState::AppView::init() +{ + // Notify app to create a new view + if (_state->_manager.valid()) + _state->_manager->doCreateView(this); + _valid = true; +} + +XRState::AppView::~AppView() +{ + destroy(); +} + +void XRState::AppView::destroy() +{ + // Notify app to destroy this view + if (_valid && _state->_manager.valid()) + _state->_manager->doDestroyView(this); + _valid = false; +} + +XRState::SlaveCamsAppView::SlaveCamsAppView(XRState *state, + uint32_t viewIndex, + osgViewer::GraphicsWindow *window, + osgViewer::View *osgView) : + AppView(state, window, osgView), + _viewIndex(viewIndex) +{ +} + +void XRState::SlaveCamsAppView::addSlave(osg::Camera *slaveCamera) +{ + XRView *xrView = _state->_xrViews[_viewIndex]; + xrView->setupCamera(slaveCamera); + xrView->getSwapchain()->incNumDrawPasses(); + + osg::ref_ptr<osg::MatrixTransform> visMaskTransform; + // Set up visibility mask for this slave camera + // We'll keep track of the transform in the slave callback so it can be + // positioned at the appropriate range + if (_state->needsVisibilityMask(slaveCamera)) + _state->setupVisibilityMask(slaveCamera, _viewIndex, visMaskTransform); + + osg::View::Slave *slave = _osgView->findSlaveForCamera(slaveCamera); + slave->_updateSlaveCallback = new SlaveCamsUpdateSlaveCallback(_viewIndex, _state, visMaskTransform.get()); +} + +void XRState::SlaveCamsAppView::removeSlave(osg::Camera *slaveCamera) +{ + XRView *xrView = _state->_xrViews[_viewIndex]; + xrView->getSwapchain()->decNumDrawPasses(); +} + +XRState::SceneViewAppView::SceneViewAppView(XRState *state, + osgViewer::GraphicsWindow *window, + osgViewer::View *osgView) : + AppView(state, window, osgView) +{ +} + +void XRState::SceneViewAppView::addSlave(osg::Camera *slaveCamera) +{ + _state->setupSceneViewCamera(slaveCamera); + _state->_xrViews[0]->getSwapchain()->incNumDrawPasses(2); + + osg::ref_ptr<osg::MatrixTransform> visMaskTransform; + // Set up visibility masks for this slave camera + // We'll keep track of the transform in the slave callback so it can be + // positioned at the appropriate range + if (_state->needsVisibilityMask(slaveCamera)) + _state->setupSceneViewVisibilityMasks(slaveCamera, visMaskTransform); + + if (visMaskTransform.valid()) + { + osg::View::Slave *slave = _osgView->findSlaveForCamera(slaveCamera); + slave->_updateSlaveCallback = new SceneViewUpdateSlaveCallback(_state, visMaskTransform.get()); + } +} + +void XRState::SceneViewAppView::removeSlave(osg::Camera *slaveCamera) +{ + _state->_xrViews[0]->getSwapchain()->decNumDrawPasses(2); +} + +std::shared_ptr<Subaction::Private> XRState::getSubaction(const std::string &path) +{ + auto it = _subactions.find(path); + if (it != _subactions.end()) + { + auto ret = (*it).second.lock(); + if (ret) + return ret; + } + + auto subaction = std::make_shared<Subaction::Private>(this, path); + _subactions[path] = subaction; + return subaction; +} + +InteractionProfile *XRState::getCurrentInteractionProfile(const OpenXR::Path &subactionPath) const +{ + if (_session.valid()) + { + // Find the path of the current profile + OpenXR::Path profilePath = _session->getCurrentInteractionProfile(subactionPath); + if (!profilePath.valid()) + return nullptr; + + // Compare against the paths of known interaction profiles + for (auto *profile: _interactionProfiles) + if (profile->getPath() == profilePath) + return profile->getPublic(); + } + return nullptr; +} + +const char *XRState::getStateString() const +{ + static const char *vrStateNames[VRSTATE_MAX] = { + "disabled", + "inactive", + "detected", + "session", + "actions", + }; + static const char *sessionStateNames[] = { + "unknown", + "idle", + "starting", + "invisible", + "visible unfocused", + "focused", + "stopping", + "loss pending", + "ending" + }; + static const char *vrStateChangeNames[VRSTATE_MAX + 1][VRSTATE_MAX] = { + { // down = VRSTATE_DISABLED + "disabling", // up = VRSTATE_DISABLED + "reinitialising", // up = VRSTATE_INSTANCE + "reinitialising & probing", // up = VRSTATE_SYSTEM + "restarting session", // up = VRSTATE_SESSION + "restarting" // up = VRSTATE_ACTIONS + }, + { // down = VRSTATE_INSTANCE + nullptr, // up = VRSTATE_DISABLED + "deactivating", // up = VRSTATE_INSTANCE + "reprobing", // up = VRSTATE_SYSTEM + "reprobing session", // up = VRSTATE_SESSION + "reprobing session" // up = VRSTATE_ACTIONS + }, + { // down = VRSTATE_SYSTEM + nullptr, // up = VRSTATE_DISABLED + nullptr, // up = VRSTATE_INSTANCE + "ending session", // up = VRSTATE_SYSTEM + "restarting session", // up = VRSTATE_SESSION + "restarting" // up = VRSTATE_ACTIONS + }, + { // down = VRSTATE_SESSION + nullptr, // up = VRSTATE_DISABLED + nullptr, // up = VRSTATE_INSTANCE + nullptr, // up = VRSTATE_SYSTEM + nullptr, // up = VRSTATE_SESSION + "attaching actions" // up = VRSTATE_ACTIONS + }, + { // down = VRSTATE_ACTIONS + nullptr, // up = VRSTATE_DISABLED + nullptr, // up = VRSTATE_INSTANCE + nullptr, // up = VRSTATE_SYSTEM + nullptr, // up = VRSTATE_SESSION + nullptr, // up = VRSTATE_ACTIONS + }, + { // down = VRSTATE_MAX + nullptr, // up = VRSTATE_DISABLED + "initialising", // up = VRSTATE_INSTANCE + "probing", // up = VRSTATE_SYSTEM + "starting session", // up = VRSTATE_SESSION + "attaching actions" // up = VRSTATE_ACTIONS + }, + }; + + std::string out = vrStateNames[_currentState]; + if (_currentState >= VRSTATE_SESSION) + { + out += " "; + out += sessionStateNames[_session->getState()]; + } + if (isStateUpdateNeeded()) + { + const char *str = vrStateChangeNames[_downState][_upState]; + if (str) + { + out += " ("; + out += str; + out += ")"; + } + } + + _stateString = out; + return _stateString.c_str(); +} + +bool XRState::hasValidationLayer() const +{ + if (!_probed) + probe(); + return _hasValidationLayer; +} + +bool XRState::hasDepthInfoExtension() const +{ + if (!_probed) + probe(); + return _hasDepthInfoExtension; +} + +bool XRState::hasVisibilityMaskExtension() const +{ + if (!_probed) + probe(); + return _hasVisibilityMaskExtension; +} + +void XRState::syncSettings() +{ + unsigned int diff = _settingsCopy._diff(*_settings.get()); + if (diff & (Settings::DIFF_APP_INFO | + Settings::DIFF_VALIDATION_LAYER)) + // Recreate instance + setDownState(VRSTATE_DISABLED); + else if (diff & (Settings::DIFF_FORM_FACTOR | + Settings::DIFF_BLEND_MODE)) + // Reread system + setDownState(VRSTATE_INSTANCE); + else if (diff & (Settings::DIFF_DEPTH_INFO | + Settings::DIFF_VISIBILITY_MASK | + Settings::DIFF_VR_MODE | + Settings::DIFF_SWAPCHAIN_MODE)) + // Recreate session + setDownState(VRSTATE_SYSTEM); +} + +bool XRState::getActionsUpdated() const +{ + // Have action sets or interaction profiles been added or removed? + if (_actionsUpdated) + return true; + + // Have action sets or their actions been altered? + for (auto *actionSet: _actionSets) + if (actionSet->getUpdated()) + return true; + + // Have interaction profile bindings been altered? + for (auto *interactionProfile: _interactionProfiles) + if (interactionProfile->getUpdated()) + return true; + + return false; +} + +void XRState::syncActionSetup() +{ + // Nothing is required if actions haven't been attached yet + if (_currentState < VRSTATE_ACTIONS) + return; + + // Restart session if actions have been updated + if (getActionsUpdated()) + setDownState(VRSTATE_SYSTEM); +} + +bool XRState::checkAndResetStateChanged() +{ + bool ret = _stateChanged; + _stateChanged = false; + return ret; +} + +void XRState::update() +{ + assert(_manager.valid()); + + typedef UpResult (XRState::*UpHandler)(); + static UpHandler upStateHandlers[VRSTATE_MAX - 1] = { + &XRState::upInstance, + &XRState::upSystem, + &XRState::upSession, + &XRState::upActions, + }; + typedef DownResult (XRState::*DownHandler)(); + static DownHandler downStateHandlers[VRSTATE_MAX - 1] = { + &XRState::downInstance, + &XRState::downSystem, + &XRState::downSession, + &XRState::downActions, + }; + + bool pollNeeded = true; + for (;;) + { + // Poll first + if (pollNeeded && _instance.valid() && _instance->valid()) + { + // Poll for events + _instance->pollEvents(this); + + // Sync actions + if (_session.valid()) + _session->syncActions(); + + // Check for instance lost + if (_instance->lost()) + setDownState(VRSTATE_DISABLED); + + pollNeeded = false; + } + // Then down transitions + else if (_downState < _currentState) + { + DownResult res = (this->*downStateHandlers[_currentState-1])(); + if (res == DOWN_SUCCESS) + { + _currentState = (VRState)((int)_currentState - 1); + if (_currentState == _downState) + _downState = VRSTATE_MAX; + _stateChanged = true; + } + else // DOWN_SOON + { + break; + } + } + // Then up transitions + else if (_upState > _currentState) + { + if (_upDelay > 0) + { + // try again soon + --_upDelay; + break; + } + UpResult res = (this->*upStateHandlers[_currentState])(); + if (res == UP_SUCCESS) + { + if (_currentState <= _downState) + _downState = VRSTATE_MAX; + _currentState = (VRState)((int)_currentState + 1); + // Poll events again after bringing up session + if (_currentState >= VRSTATE_SESSION) + pollNeeded = true; + _stateChanged = true; + } + else if (res == UP_ABORT) + { + VRState probingState = getProbingState(); + if (probingState < _currentState) + // Drop down to probing state + setDestState(getProbingState()); + else + // Go up no further + _upState = _currentState; + _stateChanged = true; + } + else // UP_SOON or UP_LATER + { + if (res == UP_LATER) + { + // Don't poll incessantly + _upDelay = 100; + } + break; + } + } + else + { + _upDelay = 0; + break; + } + } + + // Restart threading in case we had to disable it to prevent the GL context + // being bound in another thread during certain OpenXR calls. + if (_viewer.valid()) + _viewer->startThreading(); +} + +void XRState::onInstanceLossPending(OpenXR::Instance *instance, + const XrEventDataInstanceLossPending *event) +{ + // Reinitialize instance + setDownState(VRSTATE_DISABLED); + // FIXME use event.lossTime? + _upDelay = 100; +} + +void XRState::onInteractionProfileChanged(OpenXR::Session *session, + const XrEventDataInteractionProfileChanged *event) +{ + // notify subactions so they can invalidate their cached current profile + for (auto &pair: _subactions) + { + auto subaction = pair.second.lock(); + if (subaction) + subaction->onInteractionProfileChanged(session); + } +} + +void XRState::onSessionStateChanged(OpenXR::Session *session, + const XrEventDataSessionStateChanged *event) +{ + OpenXR::EventHandler::onSessionStateChanged(session, event); + _stateChanged = true; +} + +void XRState::onSessionStateStart(OpenXR::Session *session) +{ +} + +void XRState::onSessionStateEnd(OpenXR::Session *session, bool retry) +{ + if (!session->isExiting()) + { + // If the exit wasn't requested, drop back to a safe state + if (retry) + setDownState(VRSTATE_INSTANCE); + else + setDestState(getProbingState()); + } +} + +void XRState::onSessionStateReady(OpenXR::Session *session) +{ + assert(session == _session); + if (!session->begin(*_chosenViewConfig)) + { + // This should normally have succeeded + setDestState(getProbingState()); + return; + } + + // Set up cameras + switch (_vrMode) + { + case VRMode::VRMODE_SLAVE_CAMERAS: + setupSlaveCameras(); + break; + + case VRMode::VRMODE_AUTOMATIC: + // Should already have been handled by upSession() + case VRMode::VRMODE_SCENE_VIEW: + setupSceneViewCameras(); + break; + } + + // Attach a callback to detect swap + osg::ref_ptr<osg::GraphicsContext> gc = _window.get(); + osg::ref_ptr<SwapCallback> swapCallback = new SwapCallback(this); + gc->setSwapCallback(swapCallback); + + // Finally set up any mirrors that may be queued in the manager + if (_manager.valid()) + { + // FIXME consider + _manager->_setupMirrors(); + _manager->onRunning(); + } +} + +void XRState::onSessionStateStopping(OpenXR::Session *session, bool loss) +{ + // check no frame in progress + + // clean up appViews + for (auto appView: _appViews) + appView->destroy(); + _appViews.resize(0); + + osg::ref_ptr<osg::GraphicsContext> gc = _window.get(); + gc->setSwapCallback(nullptr); + + if (!loss) + session->end(); + + if (_manager.valid()) + _manager->onStopped(); +} + +void XRState::onSessionStateFocus(OpenXR::Session *session) +{ + if (_manager.valid()) + _manager->onFocus(); +} + +void XRState::onSessionStateUnfocus(OpenXR::Session *session) +{ + if (_manager.valid()) + _manager->onUnfocus(); +} + +void XRState::probe() const +{ + _hasValidationLayer = OpenXR::Instance::hasLayer(XR_APILAYER_LUNARG_core_validation); + _hasDepthInfoExtension = OpenXR::Instance::hasExtension(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME); + _hasVisibilityMaskExtension = OpenXR::Instance::hasExtension(XR_KHR_VISIBILITY_MASK_EXTENSION_NAME); + + _probed = true; +} + +void XRState::unprobe() const +{ + OpenXR::Instance::invalidateLayers(); + OpenXR::Instance::invalidateExtensions(); + + _probed = false; +} + +XRState::UpResult XRState::upInstance() +{ + assert(!_instance.valid()); + + // Create OpenXR instance + + // Update needed settings that may have changed + _settingsCopy.setApp(_settings->getAppName(), _settings->getAppVersion()); + _settingsCopy.setValidationLayer(_settings->getValidationLayer()); + + _instance = new OpenXR::Instance(); + _instance->setValidationLayer(_settingsCopy.getValidationLayer()); + _instance->setDepthInfo(true); + _instance->setVisibilityMask(true); + switch (_instance->init(_settingsCopy.getAppName().c_str(), + _settingsCopy.getAppVersion())) + { + case OpenXR::Instance::INIT_SUCCESS: + break; + case OpenXR::Instance::INIT_LATER: + _instance = nullptr; + return UP_LATER; + case OpenXR::Instance::INIT_FAIL: + _instance = nullptr; + return UP_ABORT; + } + + return UP_SUCCESS; +} + +XRState::DownResult XRState::downInstance() +{ + assert(_instance.valid()); + + // This should destroy actions and action sets + for (auto *profile: _interactionProfiles) + profile->cleanupInstance(); + for (auto *actionSet: _actionSets) + actionSet->cleanupInstance(); + + for (auto &pair: _subactions) + { + auto subaction = pair.second.lock(); + if (subaction) + subaction->cleanupInstance(); + } + + if (_probed) + unprobe(); + + osg::observer_ptr<OpenXR::Instance> oldInstance = _instance; + _instance = nullptr; + assert(!oldInstance.valid()); + + return DOWN_SUCCESS; +} + +XRState::UpResult XRState::upSystem() +{ + assert(!_system); + + // Update needed settings that may have changed + _settingsCopy.setFormFactor(_settings->getFormFactor()); + _settingsCopy.setPreferredEnvBlendModeMask(_settings->getPreferredEnvBlendModeMask()); + _settingsCopy.setAllowedEnvBlendModeMask(_settings->getAllowedEnvBlendModeMask()); + + // Get OpenXR system for chosen form factor + + switch (_settingsCopy.getFormFactor()) + { + case Settings::HEAD_MOUNTED_DISPLAY: + _formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; + break; + case Settings::HANDHELD_DISPLAY: + _formFactor = XR_FORM_FACTOR_HANDHELD_DISPLAY; + break; + } + bool supported; + _system = _instance->getSystem(_formFactor, &supported); + if (!_system) + return supported ? UP_LATER : UP_ABORT; + + // Choose the first supported view configuration + + for (const auto &viewConfig: _system->getViewConfigurations()) + { + switch (viewConfig.getType()) + { + case XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO: + case XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO: + _chosenViewConfig = &viewConfig; + break; + default: + break; + } + if (_chosenViewConfig) + break; + } + if (!_chosenViewConfig) + { + OSG_WARN << "XRState::XRState(): No supported view configuration" << std::endl; + _system = nullptr; + return UP_ABORT; + } + + // Choose an environment blend mode + + for (XrEnvironmentBlendMode envBlendMode: _chosenViewConfig->getEnvBlendModes()) + { + if ((unsigned int)envBlendMode > 31) + continue; + uint32_t mask = (1u << (unsigned int)envBlendMode); + if (_settingsCopy.getPreferredEnvBlendModeMask() & mask) + { + _chosenEnvBlendMode = envBlendMode; + break; + } + if (_chosenEnvBlendMode != XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM && + _settingsCopy.getAllowedEnvBlendModeMask() & mask) + { + _chosenEnvBlendMode = envBlendMode; + } + } + if (_chosenEnvBlendMode == XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM) + { + OSG_WARN << "XRState::XRState(): No supported environment blend mode" << std::endl; + _system = nullptr; + return UP_ABORT; + } + + return UP_SUCCESS; +} + +XRState::DownResult XRState::downSystem() +{ + _system = nullptr; + _instance->invalidateSystem(_formFactor); + return DOWN_SUCCESS; +} + +XRState::UpResult XRState::upSession() +{ + assert(_system); + assert(!_session.valid()); + + if (!_window.valid() || !_view.valid()) + // Maybe window & view haven't been initialized yet + return UP_SOON; + + // Update needed settings that may have changed + _settingsCopy.setDepthInfo(_settings->getDepthInfo()); + _settingsCopy.setVisibilityMask(_settings->getVisibilityMask()); + _settingsCopy.setVRMode(_settings->getVRMode()); + _settingsCopy.setSwapchainMode(_settings->getSwapchainMode()); + _useDepthInfo = _settingsCopy.getDepthInfo(); + _useVisibilityMask = _settingsCopy.getVisibilityMask(); + _vrMode = _settingsCopy.getVRMode(); + _swapchainMode = _settingsCopy.getSwapchainMode(); + + if (_useDepthInfo && !_instance->supportsCompositionLayerDepth()) + { + OSG_WARN << "osgXR: CompositionLayerDepth extension not supported, depth info will be disabled" << std::endl; + _useDepthInfo = false; + } + if (_useVisibilityMask && !_instance->supportsVisibilityMask()) + { + OSG_WARN << "osgXR: VisibilityMask extension not supported, visibility masking will be disabled" << std::endl; + _useVisibilityMask = false; + } + + // Decide on the algorithm to use. SceneView mode is faster. + if (_vrMode == VRMode::VRMODE_AUTOMATIC) + _vrMode = VRMode::VRMODE_SCENE_VIEW; + + // SceneView mode only works with a stereo view config + if (_vrMode == VRMode::VRMODE_SCENE_VIEW && + _chosenViewConfig->getType() != XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO) + { + _vrMode = VRMode::VRMODE_SLAVE_CAMERAS; + if (_settingsCopy.getVRMode() == VRMode::VRMODE_SCENE_VIEW) + OSG_WARN << "osgXR: No stereo view config for VR mode SCENE_VIEW, falling back to SLAVE_CAMERAS" << std::endl; + } + + // SceneView mode requires a single swapchain + if (_vrMode == VRMode::VRMODE_SCENE_VIEW) + { + if (_swapchainMode != SwapchainMode::SWAPCHAIN_AUTOMATIC && + _swapchainMode != SwapchainMode::SWAPCHAIN_SINGLE) + { + OSG_WARN << "osgXR: Overriding VR swapchain mode to SINGLE for VR mode SCENE_VIEW" << std::endl; + } + _swapchainMode = SwapchainMode::SWAPCHAIN_SINGLE; + } + + // Decide on a swapchain mode to use + if (_swapchainMode == SwapchainMode::SWAPCHAIN_AUTOMATIC) + _swapchainMode = SwapchainMode::SWAPCHAIN_MULTIPLE; + + // Stop threading to prevent the GL context being bound in another thread + // during certain OpenXR calls (session & swapchain handling). + if (_viewer.valid()) + _viewer->stopThreading(); + + // Create session using the GraphicsWindow + _session = new OpenXR::Session(_system, _window.get()); + if (!_session) + { + OSG_WARN << "XRState::init(): No suitable GraphicsWindow to create an OpenXR session" << std::endl; + return UP_ABORT; + } + + // Decide on ideal depth bits + unsigned int bestDepthBits = 24; + auto *traits = _window->getTraits(); + if (traits) + bestDepthBits = traits->depth; + + // Choose a swapchain format + int64_t chosenSwapchainFormat = 0; + int64_t chosenDepthSwapchainFormat = 0; + unsigned int chosenDepthBits = 0; + for (int64_t format: _session->getSwapchainFormats()) + { + unsigned int thisDepthBits = 0; + switch (format) + { + case GL_RGBA16: + case GL_RGB10_A2: + case GL_RGBA8: + // Choose the first supported format suggested by the runtime + if (!chosenSwapchainFormat) + chosenSwapchainFormat = format; + break; + case GL_DEPTH_COMPONENT16: + thisDepthBits = 16; + goto handle_depth; + case GL_DEPTH_COMPONENT24: + thisDepthBits = 24; + goto handle_depth; + case GL_DEPTH_COMPONENT32: + thisDepthBits = 32; + // fall through + handle_depth: + if (_useDepthInfo) + { + if (// Anything is better than nothing + !chosenDepthSwapchainFormat || + // A higher number of bits is better than not enough + (thisDepthBits > chosenDepthBits && chosenDepthBits < bestDepthBits) || + // A lower number of bits may still be enough + (bestDepthBits < thisDepthBits && thisDepthBits < chosenDepthBits)) + { + chosenDepthSwapchainFormat = format; + chosenDepthBits = thisDepthBits; + } + } + break; + default: + break; + } + } + if (!chosenSwapchainFormat) + { + std::stringstream formats; + formats << std::hex; + for (int64_t format: _session->getSwapchainFormats()) + formats << " 0x" << format; + OSG_WARN << "XRState::init(): No supported swapchain format found in [" + << formats.str() << " ]" << std::endl; + _session = nullptr; + return UP_ABORT; + } + if (_useDepthInfo && !chosenDepthSwapchainFormat) + { + std::stringstream formats; + formats << std::hex; + for (int64_t format: _session->getSwapchainFormats()) + formats << " 0x" << format; + OSG_WARN << "XRState::init(): No supported depth swapchain format found in [" + << formats.str() << " ]" << std::endl; + _useDepthInfo = false; + } + + // Set up swapchains & viewports + switch (_swapchainMode) + { + case SwapchainMode::SWAPCHAIN_SINGLE: + if (!setupSingleSwapchain(chosenSwapchainFormat, + chosenDepthSwapchainFormat)) + { + _session = nullptr; + return UP_ABORT; + } + break; + + case SwapchainMode::SWAPCHAIN_AUTOMATIC: + // Should already have been handled by upSession() + case SwapchainMode::SWAPCHAIN_MULTIPLE: + if (!setupMultipleSwapchains(chosenSwapchainFormat, + chosenDepthSwapchainFormat)) + { + _session = nullptr; + return UP_ABORT; + } + break; + } + + return UP_SUCCESS; +} + +XRState::DownResult XRState::downSession() +{ + assert(_session.valid()); + + if (_session->isRunning()) + { + if (!_session->isExiting()) + _session->requestExit(); + return DOWN_SOON; + } + + // no frames should be in progress + + // Stop threading to prevent the GL context being bound in another thread + // during certain OpenXR calls (session & swapchain destruction). + if (_viewer.valid()) + _viewer->stopThreading(); + + // Ensure the GL context is active for destruction of FBOs in XRFramebuffer + _session->makeCurrent(); + _xrViews.resize(0); + _session->releaseContext(); + + // this will destroy the session + for (auto *actionSet: _actionSets) + actionSet->cleanupSession(); + for (auto &pair: _subactions) + { + auto subaction = pair.second.lock(); + if (subaction) + subaction->cleanupSession(); + } + osg::observer_ptr<OpenXR::Session> oldSession = _session; + _session = nullptr; + assert(!oldSession.valid()); + + return DOWN_SUCCESS; +} + +XRState::UpResult XRState::upActions() +{ + // Wait until the app has set up action sets and interaction profiles + if (_actionSets.empty() || _interactionProfiles.empty()) + return UP_SOON; + + // Set up anything needed for interaction profiles + for (auto *profile: _interactionProfiles) + profile->setup(_instance); + + // Attach action sets to the session + for (auto *actionSet: _actionSets) + actionSet->setup(_session); + if (_session->attachActionSets()) + _actionsUpdated = false; + // Treat attach fail as success, as VR can still continue without input + return UP_SUCCESS; +} + +XRState::DownResult XRState::downActions() +{ + // Action setup cannot be undone + return DOWN_SUCCESS; +} + +bool XRState::setupSingleSwapchain(int64_t format, int64_t depthFormat) +{ + const auto &views = _chosenViewConfig->getViews(); + _xrViews.reserve(views.size()); + + // Arrange viewports on a single swapchain image + OpenXR::System::ViewConfiguration::View singleView(0, 0); + std::vector<OpenXR::System::ViewConfiguration::View::Viewport> viewports; + viewports.resize(views.size()); + for (uint32_t i = 0; i < views.size(); ++i) + viewports[i] = singleView.tileHorizontally(views[i]); + + // Create a single swapchain + osg::ref_ptr<XRSwapchain> xrSwapchain = new XRSwapchain(this, _session, + singleView, format, + depthFormat); + // And the views + _xrViews.reserve(views.size()); + for (uint32_t i = 0; i < views.size(); ++i) + { + osg::ref_ptr<XRView> xrView = new XRView(this, i, xrSwapchain, + viewports[i]); + if (!xrView.valid()) + { + _xrViews.resize(0); + return false; // failure + } + _xrViews.push_back(xrView); + } + + return true; +} + +bool XRState::setupMultipleSwapchains(int64_t format, int64_t depthFormat) +{ + const auto &views = _chosenViewConfig->getViews(); + _xrViews.reserve(views.size()); + + for (uint32_t i = 0; i < views.size(); ++i) + { + const auto &vcView = views[i]; + osg::ref_ptr<XRSwapchain> xrSwapchain = new XRSwapchain(this, _session, + vcView, format, + depthFormat); + osg::ref_ptr<XRView> xrView = new XRView(this, i, xrSwapchain); + if (!xrView.valid()) + { + _xrViews.resize(0); + return false; // failure + } + _xrViews.push_back(xrView); + } + + return true; +} + +void XRState::setupSlaveCameras() +{ + osg::ref_ptr<osg::GraphicsContext> gc = _window.get(); + osg::Camera *camera = _view.valid() ? _view->getCamera() : nullptr; + //camera->setName("Main"); + + _appViews.resize(_xrViews.size()); + for (uint32_t i = 0; i < _xrViews.size(); ++i) + { + SlaveCamsAppView *appView = new SlaveCamsAppView(this, i, _window.get(), + _view.get()); + appView->init(); + _appViews[i] = appView; + + if (camera && !_manager.valid()) + { + // The app isn't using a manager class, so create the new slave + // camera ourselves + osg::ref_ptr<osg::Camera> cam = new osg::Camera(); + cam->setClearColor(camera->getClearColor()); + cam->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + cam->setGraphicsContext(gc); + + // Add as a slave to the OSG view + if (!_view->addSlave(cam.get(), osg::Matrix::identity(), + osg::Matrix::identity(), true)) + { + OSG_WARN << "XRState::init(): Couldn't add slave camera" << std::endl; + continue; + } + + // And ensure it gets configured for VR + appView->addSlave(cam.get()); + } + } + + if (camera && !_manager.valid()) + { + // Disable rendering of main camera since its being overwritten by the swap texture anyway + camera->setGraphicsContext(nullptr); + } +} + +void XRState::setupSceneViewCameras() +{ + _stereoDisplaySettings = new osg::DisplaySettings(*osg::DisplaySettings::instance().get()); + _stereoDisplaySettings->setStereo(true); + _stereoDisplaySettings->setStereoMode(osg::DisplaySettings::HORIZONTAL_SPLIT); + _stereoDisplaySettings->setSplitStereoHorizontalEyeMapping(osg::DisplaySettings::LEFT_EYE_LEFT_VIEWPORT); + _stereoDisplaySettings->setUseSceneViewForStereoHint(true); + + _appViews.resize(1); + SceneViewAppView *appView = new SceneViewAppView(this, _window.get(), + _view.get()); + appView->init(); + _appViews[0] = appView; + + if (_view.valid() && !_manager.valid()) + { + // If the main camera is for rendering, set up that + osg::ref_ptr<osg::Camera> camera = _view->getCamera(); + if (camera->getGraphicsContext() != nullptr) + { + _appViews[0]->addSlave(camera); + } + else + { + // Otherwise, we'll have to go and poke about in the slave cameras + unsigned int numSlaves = _view->getNumSlaves(); + for (unsigned int i = 0; i < numSlaves; ++i) + { + osg::ref_ptr<osg::Camera> slaveCam = _view->getSlave(i)._camera; + if (slaveCam->getRenderTargetImplementation() == osg::Camera::FRAME_BUFFER) + { + OSG_WARN << "XRState::setupSceneViewCameras(): slave " << slaveCam->getName() << std::endl; + _appViews[0]->addSlave(slaveCam); + } + } + + if (!_xrViews[0]->getSwapchain()->getNumDrawPasses()) + { + OSG_WARN << "XRState::setupSceneViewCameras(): Failed to find suitable slave camera" << std::endl; + return; + } + } + } +} + +void XRState::setupSceneViewCamera(osg::Camera *camera) +{ + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + camera->setDrawBuffer(GL_COLOR_ATTACHMENT0_EXT); + camera->setReadBuffer(GL_COLOR_ATTACHMENT0_EXT); + + // Here we avoid doing anything regarding OSG camera RTT attachment. + // Ideally we would use automatic methods within OSG for handling RTT but in this + // case it seemed simpler to handle FBO creation and selection within this class. + + // This initial draw callback is used to disable normal OSG camera setup which + // would undo our RTT FBO configuration. + camera->setInitialDrawCallback(new InitialDrawCallback(this)); + + camera->setPreDrawCallback(new PreDrawCallback(_xrViews[0]->getSwapchain())); + camera->setFinalDrawCallback(new PostDrawCallback(_xrViews[0]->getSwapchain())); + + // Set the viewport (seems to need redoing!) + camera->setViewport(0, 0, + _xrViews[0]->getSwapchain()->getWidth(), + _xrViews[0]->getSwapchain()->getHeight()); + + // Set the stereo matrices callback on each SceneView + osgViewer::Renderer *renderer = static_cast<osgViewer::Renderer *>(camera->getRenderer()); + for (unsigned int i = 0; i < 2; ++i) + { + osgUtil::SceneView *sceneView = renderer->getSceneView(i); + sceneView->setComputeStereoMatricesCallback( + new ComputeStereoMatricesCallback(this, sceneView)); + } + + camera->setDisplaySettings(_stereoDisplaySettings); +} + +void XRState::setupSceneViewVisibilityMasks(osg::Camera *camera, + osg::ref_ptr<osg::MatrixTransform> &transform) +{ + for (uint32_t i = 0; i < _xrViews.size(); ++i) + { + osg::ref_ptr<osg::Geode> geode = setupVisibilityMask(camera, i, transform); + if (geode.valid()) + { + if (i == 0) + geode->setNodeMask(_visibilityMaskLeft); + else + geode->setNodeMask(_visibilityMaskRight); + } + } +} + +osg::ref_ptr<osg::Geode> XRState::setupVisibilityMask(osg::Camera *camera, uint32_t viewIndex, + osg::ref_ptr<osg::MatrixTransform> &transform) +{ + osg::ref_ptr<osg::Geometry> geometry; + geometry = _session->getVisibilityMask(viewIndex, + XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR); + if (!geometry.valid()) + return nullptr; + + osg::ref_ptr<osg::Geode> geode = new osg::Geode; + geode->setCullingActive(false); + geode->addDrawable(geometry); + + osg::ref_ptr<osg::StateSet> state = geode->getOrCreateStateSet(); + int forceOff = osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED; + state->setMode(GL_LIGHTING, forceOff); + state->setAttribute(new osg::ColorMask(false, false, false, false), + osg::StateAttribute::OVERRIDE); + state->setAttribute(new osg::Depth(osg::Depth::ALWAYS, 0.0f, 0.0f, true), + osg::StateAttribute::OVERRIDE); + state->setRenderBinDetails(INT_MIN, "RenderBin"); + + if (!transform.valid()) + { + transform = new osg::MatrixTransform; + transform->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + } + transform->addChild(geode); + + camera->addChild(transform); + + return geode; +} + +osg::ref_ptr<OpenXR::Session::Frame> XRState::getFrame(osg::FrameStamp *stamp) +{ + // Fast path + osg::ref_ptr<OpenXR::Session::Frame> frame = _frames.getFrame(stamp); + if (frame.valid()) + return frame; + + if (!_session->isRunning()) + return nullptr; + + // Slow path + return _frames.getFrame(stamp, _session); +} + +void XRState::startRendering(osg::FrameStamp *stamp) +{ + osg::ref_ptr<OpenXR::Session::Frame> frame = getFrame(stamp); + if (frame.valid() && !frame->hasBegun()) + { + frame->begin(); + _projectionLayer = new OpenXR::CompositionLayerProjection(_xrViews.size()); + _projectionLayer->setLayerFlags(XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT); + _projectionLayer->setSpace(_session->getLocalSpace()); + } +} + +void XRState::endFrame(osg::FrameStamp *stamp) +{ + osg::ref_ptr<OpenXR::Session::Frame> frame = _frames.getFrame(stamp); + if (!frame.valid()) + { + OSG_WARN << "OpenXR frame not waited for" << std::endl; + return; + } + if (!frame->hasBegun()) + { + OSG_WARN << "OpenXR frame not begun" << std::endl; + return; + } + for (auto &view: _xrViews) + view->endFrame(frame); + frame->setEnvBlendMode(_chosenEnvBlendMode); + frame->addLayer(_projectionLayer.get()); + _frames.endFrame(stamp); +} + +void XRState::updateSlave(uint32_t viewIndex, osg::View& view, + osg::View::Slave& slave) +{ + bool setProjection = false; + osg::Matrix projectionMatrix; + + osg::ref_ptr<OpenXR::Session::Frame> frame = getFrame(view.getFrameStamp()); + if (frame.valid()) + { + if (frame->isPositionValid() && frame->isOrientationValid()) + { + const auto &pose = frame->getViewPose(viewIndex); + osg::Vec3 position(pose.position.x, + pose.position.y, + pose.position.z); + osg::Quat orientation(pose.orientation.x, + pose.orientation.y, + pose.orientation.z, + pose.orientation.w); + + osg::Matrix viewOffset; + viewOffset.setTrans(viewOffset.getTrans() + position * _settings->getUnitsPerMeter()); + viewOffset.preMultRotate(orientation); + viewOffset = osg::Matrix::inverse(viewOffset); + slave._viewOffset = viewOffset; + + double left, right, bottom, top, zNear, zFar; + if (view.getCamera()->getProjectionMatrixAsFrustum(left, right, + bottom, top, + zNear, zFar)) + { + const auto &fov = frame->getViewFov(viewIndex); + createProjectionFov(projectionMatrix, fov, zNear, zFar); + setProjection = true; + } + } + } + + //slave._camera->setViewMatrix(view.getCamera()->getViewMatrix() * slave._viewOffset); + slave.updateSlaveImplementation(view); + if (setProjection) + { + slave._camera->setProjectionMatrix(projectionMatrix); + } +} + +void XRState::updateVisibilityMaskTransform(osg::Camera *camera, + osg::MatrixTransform *transform) +{ + float scale = 1.0f; + double left, right, bottom, top, zNear, zFar; + if (camera->getProjectionMatrixAsFrustum(left, right, + bottom, top, + zNear, zFar)) + { + if (isinf(zFar)) + scale = zNear * 1.1; + else + scale = (zNear + zFar) / 2; + } + transform->setMatrix(osg::Matrix::translate(0, 0, -1)); + transform->postMult(osg::Matrix::scale(scale, scale, scale)); +} + +osg::Matrixd XRState::getEyeProjection(osg::FrameStamp *stamp, + uint32_t viewIndex, + const osg::Matrixd& projection) +{ + osg::ref_ptr<OpenXR::Session::Frame> frame = getFrame(stamp); + if (frame.valid()) + { + double left, right, bottom, top, zNear, zFar; + if (projection.getFrustum(left, right, + bottom, top, + zNear, zFar)) + { + const auto &fov = frame->getViewFov(viewIndex); + osg::Matrix projectionMatrix; + createProjectionFov(projectionMatrix, fov, zNear, zFar); + return projectionMatrix; + } + } + return projection; +} + +osg::Matrixd XRState::getEyeView(osg::FrameStamp *stamp, uint32_t viewIndex, + const osg::Matrixd& view) +{ + osg::ref_ptr<OpenXR::Session::Frame> frame = getFrame(stamp); + if (frame.valid()) + { + if (frame->isPositionValid() && frame->isOrientationValid()) + { + const auto &pose = frame->getViewPose(viewIndex); + osg::Vec3 position(pose.position.x, + pose.position.y, + pose.position.z); + osg::Quat orientation(pose.orientation.x, + pose.orientation.y, + pose.orientation.z, + pose.orientation.w); + + osg::Matrix viewOffset; + viewOffset.setTrans(viewOffset.getTrans() + position * _settings->getUnitsPerMeter()); + viewOffset.preMultRotate(orientation); + viewOffset = osg::Matrix::inverse(viewOffset); + return view * viewOffset; + } + } + return view; +} + +void XRState::initialDrawCallback(osg::RenderInfo &renderInfo) +{ + osg::GraphicsOperation *graphicsOperation = renderInfo.getCurrentCamera()->getRenderer(); + osgViewer::Renderer *renderer = dynamic_cast<osgViewer::Renderer*>(graphicsOperation); + if (renderer != nullptr) + { + // Disable normal OSG FBO camera setup because it will undo the MSAA FBO configuration. + renderer->setCameraRequiresSetUp(false); + } + + startRendering(renderInfo.getState()->getFrameStamp()); + + // Get up to date depth info from camera's projection matrix + _depthInfo.setZRangeFromProjection(renderInfo.getCurrentCamera()->getProjectionMatrix()); +} + +void XRState::swapBuffersImplementation(osg::GraphicsContext* gc) +{ + // Submit rendered frame to compositor + //m_device->submitFrame(); + + endFrame(gc->getState()->getFrameStamp()); + + // Blit mirror texture to backbuffer + //m_device->blitMirrorTexture(gc); + + // Run the default system swapBufferImplementation + gc->swapBuffersImplementation(); +} diff --git a/3rdparty/osgXR/src/XRState.h b/3rdparty/osgXR/src/XRState.h new file mode 100644 index 000000000..65cdbd83d --- /dev/null +++ b/3rdparty/osgXR/src/XRState.h @@ -0,0 +1,590 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_XRSTATE +#define OSGXR_XRSTATE 1 + +#include "OpenXR/ActionSet.h" +#include "OpenXR/EventHandler.h" +#include "OpenXR/Instance.h" +#include "OpenXR/InteractionProfile.h" +#include "OpenXR/System.h" +#include "OpenXR/Session.h" +#include "OpenXR/SwapchainGroup.h" +#include "OpenXR/SwapchainGroupSubImage.h" +#include "OpenXR/Compositor.h" +#include "OpenXR/DepthInfo.h" + +#include "XRFramebuffer.h" +#include "FrameStampedVector.h" +#include "FrameStore.h" + +#include <osg/DisplaySettings> +#include <osg/Referenced> +#include <osg/observer_ptr> +#include <osg/ref_ptr> + +#include <osgXR/ActionSet> +#include <osgXR/InteractionProfile> +#include <osgXR/Settings> +#include <osgXR/Subaction> +#include <osgXR/View> + +#include <memory> +#include <vector> + +namespace osg { + class FrameStamp; +} + +namespace osgViewer { + class ViewerBase; +} + +namespace osgXR { + +class Manager; + +class XRState : public OpenXR::EventHandler +{ + public: + typedef Settings::VRMode VRMode; + typedef Settings::SwapchainMode SwapchainMode; + + XRState(Settings *settings, Manager *manager = nullptr); + + /// Represents a swapchain group + class XRSwapchain : public OpenXR::SwapchainGroup + { + public: + + XRSwapchain(XRState *state, + osg::ref_ptr<OpenXR::Session> session, + const OpenXR::System::ViewConfiguration::View &view, + int64_t chosenSwapchainFormat, + int64_t chosenDepthSwapchainFormat); + + // GL context must be current (for XRFramebuffer) + virtual ~XRSwapchain(); + + void incNumDrawPasses(unsigned int num = 1) + { + _numDrawPasses += num; + } + + void decNumDrawPasses(unsigned int num = 1) + { + _numDrawPasses -= num; + } + + unsigned int getNumDrawPasses() + { + return _numDrawPasses; + } + + void setupImage(const osg::FrameStamp *stamp); + + void preDrawCallback(osg::RenderInfo &renderInfo); + void postDrawCallback(osg::RenderInfo &renderInfo); + void endFrame(); + + osg::ref_ptr<osg::Texture2D> getOsgTexture(const osg::FrameStamp *stamp); + + protected: + + XRState *_state; + FrameStampedVector<osg::ref_ptr<XRFramebuffer> > _imageFramebuffers; + + /// Number of expected draw passes. + unsigned int _numDrawPasses; + unsigned int _drawPassesDone; + bool _imagesReady; + }; + + /// Represents an OpenXR view + class XRView : public osg::Referenced + { + public: + + XRView(XRState *state, + uint32_t viewIndex, + osg::ref_ptr<XRSwapchain> swapchain); + XRView(XRState *state, + uint32_t viewIndex, + osg::ref_ptr<XRSwapchain> swapchain, + const OpenXR::System::ViewConfiguration::View::Viewport &viewport); + + // GL context must be current (for XRFramebuffer) + virtual ~XRView(); + + bool valid() const + { + return _swapchainSubImage.valid(); + } + + osg::ref_ptr<XRSwapchain> getSwapchain() + { + return static_cast<XRSwapchain *>(_swapchainSubImage.getSwapchainGroup().get()); + } + + const XRSwapchain::SubImage &getSubImage() const + { + return _swapchainSubImage; + } + + void setupCamera(osg::ref_ptr<osg::Camera> camera); + + void endFrame(OpenXR::Session::Frame *frame); + + protected: + + XRState *_state; + XRSwapchain::SubImage _swapchainSubImage; + + uint32_t _viewIndex; + }; + + /** Represents a generic app level view. + * This may handle multiple OpenXR views. + */ + class AppView : public View + { + public: + + AppView(XRState *state, + osgViewer::GraphicsWindow *window, + osgViewer::View *osgView); + virtual ~AppView(); + + void destroy(); + + void init(); + + protected: + + bool _valid; + + XRState *_state; + }; + + /// Represents an app level view in slave cams mode + class SlaveCamsAppView : public AppView + { + public: + + SlaveCamsAppView(XRState *state, + uint32_t viewIndex, + osgViewer::GraphicsWindow *window, + osgViewer::View *osgView); + + void addSlave(osg::Camera *slaveCamera) override; + void removeSlave(osg::Camera *slaveCamera) override; + + protected: + + uint32_t _viewIndex; + }; + + /// Represents an app level view in scene view mode + class SceneViewAppView : public AppView + { + public: + + SceneViewAppView(XRState *state, + osgViewer::GraphicsWindow *window, + osgViewer::View *osgView); + + void addSlave(osg::Camera *slaveCamera) override; + void removeSlave(osg::Camera *slaveCamera) override; + }; + + bool hasValidationLayer() const; + bool hasDepthInfoExtension() const; + bool hasVisibilityMaskExtension() const; + + inline const char *getRuntimeName() const + { + if (_currentState < VRSTATE_INSTANCE) + return ""; + return _instance->getRuntimeName(); + } + + inline const char *getSystemName() const + { + if (_currentState < VRSTATE_SYSTEM) + return ""; + return _system->getSystemName(); + } + + inline bool getPresent() const + { + return _instance.valid() && _instance->valid(); + } + + inline bool valid() const + { + return _currentState >= VRSTATE_SESSION; + } + + typedef enum { + /// No OpenXR instance. + VRSTATE_DISABLED = 0, + /// OpenXR instance created. + VRSTATE_INSTANCE, + /// Valid OpenXR system found. + VRSTATE_SYSTEM, + /// Session created + VRSTATE_SESSION, + /// Actions configured + VRSTATE_ACTIONS, + + VRSTATE_MAX, + } VRState; + + /// Set the init state to drop down to before returning to prior level. + void setDownState(VRState downState) + { + if (downState < _downState && downState < _currentState) + { + _downState = downState; + _stateChanged = true; + } + } + /// Get the current init state to rise up to. + VRState getUpState() const + { + return _upState; + } + /// Get the current init state to rise up to. + VRState getCurrentState() const + { + return _currentState; + } + /// Set the init state to rise up to. + void setUpState(VRState upState) + { + if (upState != _upState) + { + _upState = upState; + _stateChanged = true; + } + } + /// Set the minimum init state to rise up to. + void setMinUpState(VRState minUpState) + { + if (minUpState > _upState) + { + _upState = minUpState; + _stateChanged = true; + } + } + /// Set destination state, both up and down. + void setDestState(VRState destState) + { + setDownState(destState); + setUpState(destState); + } + /// Find if updates are needed for state changes. + bool isStateUpdateNeeded() const + { + return _currentState > _downState || _currentState < _upState; + } + + /// Find if a VR session is running. + bool isRunning() const + { + if (_currentState < VRSTATE_SESSION) + return false; + return _session->isRunning(); + } + + /// Set whether probing should be active. + void setProbing(bool probing) + { + if (_probing == probing) + return; + _probing = probing; + if (probing) + { + // Init at least up to system + setMinUpState(VRSTATE_SYSTEM); + } + else + { + // If only initing to system, shutdown + if (_upState <= VRSTATE_SYSTEM) + setDestState(VRSTATE_DISABLED); + } + } + + VRState getProbingState() const + { + return _probing ? VRSTATE_SYSTEM : VRSTATE_DISABLED; + } + + void setViewer(osgViewer::ViewerBase *viewer) + { + _viewer = viewer; + } + + /// Set the NodeMasks to use for visibility masks. + void setVisibilityMaskNodeMasks(osg::Node::NodeMask left, + osg::Node::NodeMask right) + { + _visibilityMaskLeft = left; + _visibilityMaskRight = right; + } + + /// Get the subaction object for a subaction path string. + std::shared_ptr<Subaction::Private> getSubaction(const std::string &path); + + /// Add an action set + void addActionSet(ActionSet::Private *actionSet) + { + _actionSets.insert(actionSet); + _actionsUpdated = true; + } + + /// Remove an action set + void removeActionSet(ActionSet::Private *actionSet) + { + _actionSets.erase(actionSet); + _actionsUpdated = true; + } + + /// Add an interaction profile + void addInteractionProfile(InteractionProfile::Private *interactionProfile) + { + _interactionProfiles.insert(interactionProfile); + _actionsUpdated = true; + } + + /// Remove an interaction profile + void removeInteractionProfile(InteractionProfile::Private *interactionProfile) + { + _interactionProfiles.erase(interactionProfile); + _actionsUpdated = true; + } + + /// Get the current interaction profile for the given subaction path. + InteractionProfile *getCurrentInteractionProfile(const OpenXR::Path &subactionPath) const; + + /// Get a string describing the state (for user consumption). + const char *getStateString() const; + + // Initialize information required for setting up VR + void init(osgViewer::GraphicsWindow *window, + osgViewer::View *view = nullptr) + { + _window = window; + _view = view; + } + + /// Update down state depending on any changed settings. + void syncSettings(); + + /// Find whether actions have been updated. + bool getActionsUpdated() const; + + /// Arrange reinit as needed of action setup. + void syncActionSetup(); + + /// Find whether state has changed since last call, and reset. + bool checkAndResetStateChanged(); + + /// Perform a regular update. + void update(); + + // Extending OpenXR::EventManager + void onInstanceLossPending(OpenXR::Instance *instance, + const XrEventDataInstanceLossPending *event) override; + void onInteractionProfileChanged(OpenXR::Session *session, + const XrEventDataInteractionProfileChanged *event) override; + void onSessionStateChanged(OpenXR::Session *session, + const XrEventDataSessionStateChanged *event) override; + void onSessionStateStart(OpenXR::Session *session) override; + void onSessionStateEnd(OpenXR::Session *session, bool retry) override; + void onSessionStateReady(OpenXR::Session *session) override; + void onSessionStateStopping(OpenXR::Session *session, bool loss) override; + void onSessionStateFocus(OpenXR::Session *session) override; + void onSessionStateUnfocus(OpenXR::Session *session) override; + + osg::ref_ptr<OpenXR::Session::Frame> getFrame(osg::FrameStamp *stamp); + void startRendering(osg::FrameStamp *stamp); + void endFrame(osg::FrameStamp *stamp); + + void updateSlave(uint32_t viewIndex, osg::View& view, + osg::View::Slave& slave); + void updateVisibilityMaskTransform(osg::Camera *camera, + osg::MatrixTransform *transform); + + osg::Matrixd getEyeProjection(osg::FrameStamp *stamp, + uint32_t viewIndex, + const osg::Matrixd& projection); + osg::Matrixd getEyeView(osg::FrameStamp *stamp, uint32_t viewIndex, + const osg::Matrixd& view); + + void initialDrawCallback(osg::RenderInfo &renderInfo); + void swapBuffersImplementation(osg::GraphicsContext* gc); + + inline osg::ref_ptr<OpenXR::CompositionLayerProjection> getProjectionLayer() + { + return _projectionLayer; + } + + class TextureRect + { + public: + + float x, y; + float width, height; + + TextureRect(const OpenXR::SwapchainGroup::SubImage &subImage) + { + float w = subImage.getSwapchainGroup()->getWidth(); + float h = subImage.getSwapchainGroup()->getHeight(); + x = (float)subImage.getX() / w; + y = (float)subImage.getY() / h; + width = (float)subImage.getWidth() / w; + height = (float)subImage.getHeight() / h; + } + }; + + unsigned int getViewCount() const + { + return _xrViews.size(); + } + + TextureRect getViewTextureRect(unsigned int viewIndex) const + { + return TextureRect(_xrViews[viewIndex]->getSubImage()); + } + + // Caller must validate viewIndex using getViewCount() + osg::ref_ptr<osg::Texture2D> getViewTexture(unsigned int viewIndex, + const osg::FrameStamp *stamp) const + { + return _xrViews[viewIndex]->getSwapchain()->getOsgTexture(stamp); + } + + protected: + + typedef enum { + /// Successfully completed operation. + UP_SUCCESS, + /// Operation not possible at the moment, try again soon. + UP_SOON, + /// Operation not possible at the moment, try again later. + UP_LATER, + /// Operation permanently failed, disable VR. + UP_ABORT, + } UpResult; + + typedef enum { + /// Successfully completed operation. + DOWN_SUCCESS, + /// Operation not possible at the moment, try again soon. + DOWN_SOON, + } DownResult; + + // Pre-instance probing + void probe() const; + void unprobe() const; + + // These are called during update to raise or lower VR state level + UpResult upInstance(); + DownResult downInstance(); + UpResult upSystem(); + DownResult downSystem(); + UpResult upSession(); + DownResult downSession(); + UpResult upActions(); + DownResult downActions(); + + // Set up a single swapchain containing multiple viewports + bool setupSingleSwapchain(int64_t format, int64_t depthFormat = 0); + // Set up a swapchain for each view + bool setupMultipleSwapchains(int64_t format, int64_t depthFormat = 0); + // Set up slave cameras + void setupSlaveCameras(); + // Set up SceneView VR mode cameras + void setupSceneViewCameras(); + void setupSceneViewCamera(osg::Camera *camera); + // Visibility mask setup + inline bool needsVisibilityMask(osg::Camera *camera) + { + return _useVisibilityMask && + (camera->getClearMask() & GL_DEPTH_BUFFER_BIT); + } + void setupSceneViewVisibilityMasks(osg::Camera *camera, + osg::ref_ptr<osg::MatrixTransform> &transform); + osg::ref_ptr<osg::Geode> setupVisibilityMask(osg::Camera *camera, + uint32_t viewIndex, + osg::ref_ptr<osg::MatrixTransform> &transform); + + osg::ref_ptr<Settings> _settings; + Settings _settingsCopy; + osg::observer_ptr<Manager> _manager; + + // app configuration + osg::Node::NodeMask _visibilityMaskLeft; + osg::Node::NodeMask _visibilityMaskRight; + + // Actions + bool _actionsUpdated; + std::set<ActionSet::Private *> _actionSets; + std::set<InteractionProfile::Private *> _interactionProfiles; + std::map<std::string, std::weak_ptr<Subaction::Private>> _subactions; + + /// Current state of OpenXR initialization. + VRState _currentState; + /// State of OpenXR initialisation to drop down to. + VRState _downState; + /// State of OpenXR initialisation to rise up to. + VRState _upState; + /// Number of attempts made to rise VR state. + unsigned int _upDelay; + /// Whether probing should be kept active. + bool _probing; + /// Last read state as a user readable string. + mutable std::string _stateString; + /// Whether state has changed since the last update. + bool _stateChanged; + + // Session setup + osg::observer_ptr<osgViewer::ViewerBase> _viewer; + osg::observer_ptr<osgViewer::GraphicsWindow> _window; + osg::observer_ptr<osgViewer::View> _view; + + // Pre-Instance related + mutable bool _probed; + mutable bool _hasValidationLayer; + mutable bool _hasDepthInfoExtension; + mutable bool _hasVisibilityMaskExtension; + + // Instance related + osg::ref_ptr<OpenXR::Instance> _instance; + bool _useDepthInfo; + bool _useVisibilityMask; + + // System related + XrFormFactor _formFactor; + OpenXR::System *_system; + const OpenXR::System::ViewConfiguration *_chosenViewConfig; + XrEnvironmentBlendMode _chosenEnvBlendMode; + + // Session related + VRMode _vrMode; + SwapchainMode _swapchainMode; + osg::ref_ptr<OpenXR::Session> _session; + std::vector<osg::ref_ptr<XRView> > _xrViews; + std::vector<osg::ref_ptr<AppView> > _appViews; + FrameStore _frames; + osg::ref_ptr<OpenXR::CompositionLayerProjection> _projectionLayer; + OpenXR::DepthInfo _depthInfo; + osg::ref_ptr<osg::DisplaySettings> _stereoDisplaySettings; +}; + +} // osgXR + +#endif diff --git a/3rdparty/osgXR/src/XRStateCallbacks.h b/3rdparty/osgXR/src/XRStateCallbacks.h new file mode 100644 index 000000000..45f285549 --- /dev/null +++ b/3rdparty/osgXR/src/XRStateCallbacks.h @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_XRSTATE_CALLBACKS +#define OSGXR_XRSTATE_CALLBACKS 1 + +#include "XRState.h" + +#include <osg/Camera> +#include <osg/GraphicsContext> +#include <osg/View> + +#include <osgUtil/SceneView> + +namespace osgXR { + +class SlaveCamsUpdateSlaveCallback : public osg::View::Slave::UpdateSlaveCallback +{ + public: + + SlaveCamsUpdateSlaveCallback(uint32_t viewIndex, + XRState *xrState, + osg::MatrixTransform *visMaskTransform) : + _viewIndex(viewIndex), + _xrState(xrState), + _visMaskTransform(visMaskTransform) + { + } + + void updateSlave(osg::View& view, osg::View::Slave& slave) override + { + _xrState->updateSlave(_viewIndex, view, slave); + if (_visMaskTransform.valid()) + _xrState->updateVisibilityMaskTransform(slave._camera, + _visMaskTransform.get()); + } + + protected: + + uint32_t _viewIndex; + osg::observer_ptr<XRState> _xrState; + osg::observer_ptr<osg::MatrixTransform> _visMaskTransform; +}; + +class SceneViewUpdateSlaveCallback : public osg::View::Slave::UpdateSlaveCallback +{ + public: + + SceneViewUpdateSlaveCallback(osg::ref_ptr<XRState> xrState, + osg::ref_ptr<osg::MatrixTransform> visMaskTransform) : + _xrState(xrState), + _visMaskTransform(visMaskTransform) + { + } + + void updateSlave(osg::View& view, osg::View::Slave& slave) override + { + if (_visMaskTransform.valid()) + _xrState->updateVisibilityMaskTransform(slave._camera, + _visMaskTransform.get()); + } + + protected: + + osg::observer_ptr<XRState> _xrState; + osg::observer_ptr<osg::MatrixTransform> _visMaskTransform; +}; + +class ComputeStereoMatricesCallback : public osgUtil::SceneView::ComputeStereoMatricesCallback +{ + public: + + ComputeStereoMatricesCallback(XRState *xrState, + osgUtil::SceneView *sceneView) : + _xrState(xrState), + _sceneView(sceneView) + { + } + + osg::Matrixd computeLeftEyeProjection(const osg::Matrixd& projection) const override + { + return _xrState->getEyeProjection(_sceneView->getFrameStamp(), + 0, projection); + } + + osg::Matrixd computeLeftEyeView(const osg::Matrixd& view) const override + { + return _xrState->getEyeView(_sceneView->getFrameStamp(), + 0, view); + } + + osg::Matrixd computeRightEyeProjection(const osg::Matrixd& projection) const override + { + return _xrState->getEyeProjection(_sceneView->getFrameStamp(), + 1, projection); + } + + osg::Matrixd computeRightEyeView(const osg::Matrixd& view) const override + { + return _xrState->getEyeView(_sceneView->getFrameStamp(), + 1, view); + } + + protected: + + osg::observer_ptr<XRState> _xrState; + osg::observer_ptr<osgUtil::SceneView> _sceneView; +}; + +class InitialDrawCallback : public osg::Camera::DrawCallback +{ + public: + + InitialDrawCallback(osg::ref_ptr<XRState> xrState) : + _xrState(xrState) + { + } + + void operator()(osg::RenderInfo& renderInfo) const override + { + _xrState->initialDrawCallback(renderInfo); + } + + protected: + + osg::observer_ptr<XRState> _xrState; +}; + +class PreDrawCallback : public osg::Camera::DrawCallback +{ + public: + + PreDrawCallback(osg::ref_ptr<XRState::XRSwapchain> xrSwapchain) : + _xrSwapchain(xrSwapchain) + { + } + + void operator()(osg::RenderInfo& renderInfo) const override + { + _xrSwapchain->preDrawCallback(renderInfo); + } + + protected: + + osg::observer_ptr<XRState::XRSwapchain> _xrSwapchain; +}; + +class PostDrawCallback : public osg::Camera::DrawCallback +{ + public: + + PostDrawCallback(osg::ref_ptr<XRState::XRSwapchain> xrSwapchain) : + _xrSwapchain(xrSwapchain) + { + } + + void operator()(osg::RenderInfo& renderInfo) const override + { + _xrSwapchain->postDrawCallback(renderInfo); + } + + protected: + + osg::observer_ptr<XRState::XRSwapchain> _xrSwapchain; +}; + +class SwapCallback : public osg::GraphicsContext::SwapCallback +{ + public: + + explicit SwapCallback(osg::ref_ptr<XRState> xrState) : + _xrState(xrState), + _frameIndex(0) + { + } + + void swapBuffersImplementation(osg::GraphicsContext* gc) + { + _xrState->swapBuffersImplementation(gc); + } + + int frameIndex() const + { + return _frameIndex; + } + + private: + + osg::observer_ptr<XRState> _xrState; + int _frameIndex; +}; + +} + +#endif diff --git a/3rdparty/osgXR/src/osgXR.cpp b/3rdparty/osgXR/src/osgXR.cpp new file mode 100644 index 000000000..a31147263 --- /dev/null +++ b/3rdparty/osgXR/src/osgXR.cpp @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#include <osgXR/MirrorSettings> +#include <osgXR/OpenXRDisplay> +#include <osgXR/Settings> +#include <osgXR/osgXR> + +#include <osg/Notify> +#include <osg/os_utils> + +using namespace osgXR; + +void osgXR::setupViewerDefaults(osgViewer::Viewer *viewer, + const std::string &appName, + uint32_t appVersion) +{ + unsigned int vr = 0; + osg::getEnvVar("OSGXR", vr); + + if (vr) + { + Settings *settings = Settings::instance(); + MirrorSettings *mirrorSettings = &settings->getMirrorSettings(); + std::string value; + + Settings::VRMode vrMode = Settings::VRMODE_AUTOMATIC; + if (osg::getEnvVar("OSGXR_MODE", value)) + { + if (value == "SLAVE_CAMERAS") + vrMode = Settings::VRMODE_SLAVE_CAMERAS; + else if (value == "SCENE_VIEW") + vrMode = Settings::VRMODE_SCENE_VIEW; + } + + Settings::SwapchainMode swapchainMode = Settings::SWAPCHAIN_AUTOMATIC; + if (osg::getEnvVar("OSGXR_SWAPCHAIN", value)) + { + if (value == "MULTIPLE") + swapchainMode = Settings::SWAPCHAIN_MULTIPLE; + else if (value == "SINGLE") + swapchainMode = Settings::SWAPCHAIN_SINGLE; + } + + float unitsPerMeter = 0.0f; + osg::getEnvVar("OSGXR_UNITS_PER_METER", unitsPerMeter); + + int validationLayer = 0; + osg::getEnvVar("OSGXR_VALIDATION_LAYER", validationLayer); + + int depthInfo = 0; + osg::getEnvVar("OSGXR_DEPTH_INFO", depthInfo); + + MirrorSettings::MirrorMode mirrorMode = MirrorSettings::MIRROR_AUTOMATIC; + int mirrorViewIndex = -1; + if (osg::getEnvVar("OSGXR_MIRROR", value)) + { + if (value == "NONE") + { + mirrorMode = MirrorSettings::MIRROR_NONE; + } + else if (value == "LEFT") + { + mirrorMode = MirrorSettings::MIRROR_SINGLE; + mirrorViewIndex = 0; + } + else if (value == "RIGHT") + { + mirrorMode = MirrorSettings::MIRROR_SINGLE; + mirrorViewIndex = 1; + } + else if (value == "LEFT_RIGHT") + { + mirrorMode = MirrorSettings::MIRROR_LEFT_RIGHT; + } + } + + settings->setApp(appName, appVersion); + settings->setFormFactor(Settings::HEAD_MOUNTED_DISPLAY); + settings->preferEnvBlendMode(Settings::OPAQUE); + if (unitsPerMeter > 0.0f) + settings->setUnitsPerMeter(unitsPerMeter); + settings->setVRMode(vrMode); + settings->setSwapchainMode(swapchainMode); + settings->setValidationLayer(!!validationLayer); + settings->setDepthInfo(!!depthInfo); + mirrorSettings->setMirror(mirrorMode, mirrorViewIndex); + + osg::ref_ptr<OpenXRDisplay> xr = new OpenXRDisplay(settings); + viewer->apply(xr); + + OSG_WARN << "Setting up VR" << std::endl; + } +} diff --git a/3rdparty/osgXR/src/projection.cpp b/3rdparty/osgXR/src/projection.cpp new file mode 100644 index 000000000..b7d15b73d --- /dev/null +++ b/3rdparty/osgXR/src/projection.cpp @@ -0,0 +1,79 @@ +// ============================================================================= +// Derived from openxr-simple-example +// Copyright 2019-2021, Collabora, Ltd. +// Which was adapted from +// https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/master/src/common/xr_linear.h +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2016 Oculus VR, LLC. +// SPDX-License-Identifier: Apache-2.0 +// ============================================================================= + +#include "projection.h" + +void osgXR::createProjectionFov(osg::Matrix& result, + const XrFovf& fov, + const float nearZ, + const float farZ) +{ + const float tanAngleLeft = tanf(fov.angleLeft); + const float tanAngleRight = tanf(fov.angleRight); + + const float tanAngleDown = tanf(fov.angleDown); + const float tanAngleUp = tanf(fov.angleUp); + + const float tanAngleWidth = tanAngleRight - tanAngleLeft; + + // Set to tanAngleDown - tanAngleUp for a clip space with positive Y + // down (Vulkan). Set to tanAngleUp - tanAngleDown for a clip space with + // positive Y up (OpenGL / D3D / Metal). + const float tanAngleHeight = tanAngleUp - tanAngleDown; + + // Set to nearZ for a [-1,1] Z clip space (OpenGL / OpenGL ES). + // Set to zero for a [0,1] Z clip space (Vulkan / D3D / Metal). + const float offsetZ = nearZ; + + if (farZ <= nearZ) + { + // place the far plane at infinity + result(0, 0) = 2 / tanAngleWidth; + result(1, 0) = 0; + result(2, 0) = (tanAngleRight + tanAngleLeft) / tanAngleWidth; + result(3, 0) = 0; + + result(0, 1) = 0; + result(1, 1) = 2 / tanAngleHeight; + result(2, 1) = (tanAngleUp + tanAngleDown) / tanAngleHeight; + result(3, 1) = 0; + + result(0, 2) = 0; + result(1, 2) = 0; + result(2, 2) = -1; + result(3, 2) = -(nearZ + offsetZ); + + result(0, 3) = 0; + result(1, 3) = 0; + result(2, 3) = -1; + result(3, 3) = 0; + } else { + // normal projection + result(0, 0) = 2 / tanAngleWidth; + result(1, 0) = 0; + result(2, 0) = (tanAngleRight + tanAngleLeft) / tanAngleWidth; + result(3, 0) = 0; + + result(0, 1) = 0; + result(1, 1) = 2 / tanAngleHeight; + result(2, 1) = (tanAngleUp + tanAngleDown) / tanAngleHeight; + result(3, 1) = 0; + + result(0, 2) = 0; + result(1, 2) = 0; + result(2, 2) = -(farZ + offsetZ) / (farZ - nearZ); + result(3, 2) = -(farZ * (nearZ + offsetZ)) / (farZ - nearZ); + + result(0, 3) = 0; + result(1, 3) = 0; + result(2, 3) = -1; + result(3, 3) = 0; + } +} diff --git a/3rdparty/osgXR/src/projection.h b/3rdparty/osgXR/src/projection.h new file mode 100644 index 000000000..96a19fc4c --- /dev/null +++ b/3rdparty/osgXR/src/projection.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: LGPL-2.1-only +// Copyright (C) 2021 James Hogan <james@albanarts.com> + +#ifndef OSGXR_PROJECTION +#define OSGXR_PROJECTION 1 + +#include <osg/Matrix> + +#include <openxr/openxr.h> + +namespace osgXR { + +void createProjectionFov(osg::Matrix& result, + const XrFovf& fov, + const float nearZ, + const float farZ); + +} // osgXR + +#endif