diff --git a/CMakeLists.txt b/CMakeLists.txt index 220c3e557..671f83980 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,6 +151,11 @@ elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux" OR if(HTS_ENGINE_FOUND) set(SYSTEM_HTS_ENGINE_DEFAULT 1) endif() + + find_package(osgXR 0.3) + if (osgXR_FOUND) + set(ENABLE_OSGXR_DEFAULT 1) + endif() endif() # FlightGear build options @@ -171,6 +176,7 @@ option(SYSTEM_GSM "Set to ON to build IAXClient with the system's GSM lib option(SYSTEM_FLITE "Set to ON to build Flightgear with the system's Flite library" ${SYSTEM_FLITE_DEFAULT}) option(SYSTEM_HTS_ENGINE "Set to ON to build Flightgear with the system's HTS Engine library" ${SYSTEM_HTS_ENGINE_DEFAULT}) option(SYSTEM_CPPUNIT "Set to ON to build Flightgear with the system's CppUnit library") +option(ENABLE_OSGXR "Set to ON to build Flightgear with OpenXR support via the osgXR library" ${ENABLE_OSGXR_DEFAULT}) # additional utilities option(ENABLE_FGELEV "Set to ON to build the fgelev application (default)" ON) diff --git a/CMakeModules/SetupFGFSIncludes.cmake b/CMakeModules/SetupFGFSIncludes.cmake index d0d625352..d47db41a3 100644 --- a/CMakeModules/SetupFGFSIncludes.cmake +++ b/CMakeModules/SetupFGFSIncludes.cmake @@ -4,6 +4,10 @@ function(setup_fgfs_includes target) target_include_directories(${target} PRIVATE ${PROJECT_SOURCE_DIR}/src/FDM/JSBSim) endif() + if(ENABLE_OSGXR) + target_include_directories(${target} PRIVATE ${osgXR_INCLUDE_DIR}) + endif() + target_include_directories(${target} PRIVATE ${PLIB_INCLUDE_DIR}) target_include_directories(${target} PRIVATE ${PROJECT_SOURCE_DIR}/3rdparty/cjson) # only actually needed for httpd.cxx diff --git a/CMakeModules/SetupFGFSLibraries.cmake b/CMakeModules/SetupFGFSLibraries.cmake index ae6396946..ad5a23332 100644 --- a/CMakeModules/SetupFGFSLibraries.cmake +++ b/CMakeModules/SetupFGFSLibraries.cmake @@ -23,6 +23,10 @@ function(setup_fgfs_libraries target) target_link_libraries(${target} ${X11_LIBRARIES}) endif() + if(ENABLE_OSGXR) + target_link_libraries(${target} osgXR::osgXR) + endif() + target_link_libraries(${target} fgsqlite3 fgvoicesynth fgembeddedresources) target_link_libraries(${target} diff --git a/scripts/completion/fg-completion.zsh b/scripts/completion/fg-completion.zsh index 3586fedf4..7fd0dbea7 100755 --- a/scripts/completion/fg-completion.zsh +++ b/scripts/completion/fg-completion.zsh @@ -60,6 +60,8 @@ _fgfs_options=( '--enable-skyblend[Enable sky blending]' \ '--disable-textures[Disable textures]' \ '--enable-textures[Enable textures]' \ + '--disable-vr[Disable VR]' \ + '--enable-vr[Enable VR]' \ '--disable-wireframe[Disable wireframe drawing mode]' \ '--enable-wireframe[Enable wireframe drawing mode]' \ '--notrim[Do NOT attempt to trim the model (only with fdm=jsbsim)]' \ diff --git a/src/Include/config_cmake.h.in b/src/Include/config_cmake.h.in index bd69ade93..b7c67d055 100644 --- a/src/Include/config_cmake.h.in +++ b/src/Include/config_cmake.h.in @@ -27,6 +27,7 @@ #define FLIGHTGEAR_VERSION "@FLIGHTGEAR_VERSION@" #define FLIGHTGEAR_MAJOR_VERSION @FG_VERSION_MAJOR@ #define FLIGHTGEAR_MINOR_VERSION @FG_VERSION_MINOR@ +#define FLIGHTGEAR_PATCH_VERSION @FG_VERSION_PATCH@ #cmakedefine ENABLE_UIUC_MODEL #cmakedefine ENABLE_LARCSIM @@ -75,3 +76,5 @@ #cmakedefine HAVE_SENTRY #define SENTRY_API_KEY "@sentry_api_key@" + +#cmakedefine ENABLE_OSGXR diff --git a/src/Main/options.cxx b/src/Main/options.cxx index f0f9c43e3..7bb02f76b 100644 --- a/src/Main/options.cxx +++ b/src/Main/options.cxx @@ -1900,6 +1900,10 @@ struct OptionDesc { {"disable-wireframe", false, OPTION_BOOL, "/sim/rendering/wireframe", false, "", 0 }, {"enable-wireframe", false, OPTION_BOOL, "/sim/rendering/wireframe", true, "", 0 }, {"materials-file", true, OPTION_STRING, "/sim/rendering/materials-file", false, "", 0 }, +#ifdef ENABLE_OSGXR + {"disable-vr", false, OPTION_BOOL, "/sim/vr/enabled", false, "", 0 }, + {"enable-vr", false, OPTION_BOOL, "/sim/vr/enabled", true, "", 0 }, +#endif {"disable-terrasync", false, OPTION_BOOL, "/sim/terrasync/enabled", false, "", 0 }, {"enable-terrasync", false, OPTION_BOOL, "/sim/terrasync/enabled", true, "", 0 }, {"terrasync-dir", true, OPTION_IGNORE, "", false, "", 0 }, diff --git a/src/Viewer/CMakeLists.txt b/src/Viewer/CMakeLists.txt index f65fee469..6646fde85 100644 --- a/src/Viewer/CMakeLists.txt +++ b/src/Viewer/CMakeLists.txt @@ -28,8 +28,15 @@ set(HEADERS viewmgr.hxx sview.hxx GraphicsPresets.hxx + VRManager.hxx ) +if (ENABLE_OSGXR) + list(APPEND SOURCES + VRManager.cxx + ) +endif() + if (YES) list(APPEND HEADERS PUICamera.hxx) list(APPEND SOURCES PUICamera.cxx) diff --git a/src/Viewer/CameraGroup.cxx b/src/Viewer/CameraGroup.cxx index 8b326e019..e122b9c0e 100644 --- a/src/Viewer/CameraGroup.cxx +++ b/src/Viewer/CameraGroup.cxx @@ -477,7 +477,7 @@ void CameraGroup::buildDistortionCamera(const SGPropertyNode* psNode, #endif } -void CameraGroup::buildCamera(SGPropertyNode* cameraNode) +CameraInfo* CameraGroup::buildCamera(SGPropertyNode* cameraNode) { WindowBuilder *wBuild = WindowBuilder::getWindowBuilder(); const SGPropertyNode* windowNode = cameraNode->getNode("window"); @@ -491,7 +491,7 @@ void CameraGroup::buildCamera(SGPropertyNode* cameraNode) window = wBuild->buildWindow(cameraNode); } if (!window) { - return; + return nullptr; } osg::Matrix vOff; @@ -599,7 +599,7 @@ void CameraGroup::buildCamera(SGPropertyNode* cameraNode) if (it == _cameras.end()) { SG_LOG(SG_VIEW, SG_ALERT, "CameraGroup::buildCamera: " "failed to find parent camera for relative camera!"); - return; + return nullptr; } parentInfo = (*it); if (projectionNode->getNameString() == "right-of-perspective") { @@ -717,6 +717,18 @@ void CameraGroup::buildCamera(SGPropertyNode* cameraNode) info->flags = info->flags | CameraInfo::VIEW_ABSOLUTE; //buildDistortionCamera(psNode, camera); } + + return info; +} + +void CameraGroup::removeCamera(CameraInfo *info) +{ + for (auto it = _cameras.begin(); it != _cameras.end(); ++it) { + if (*it == info) { + _cameras.erase(it); + return; + } + } } void CameraGroup::buildGUICamera(SGPropertyNode* cameraNode, @@ -970,6 +982,9 @@ void reloadCompositors(CameraGroup *cgroup) SGReaderWriterOptions::fromPath(globals->get_fg_root()); options->setPropertyNode(globals->get_props()); + if (info->reloadCompositorCallback.valid()) + info->reloadCompositorCallback->preReloadCompositor(cgroup, info); + // Force deletion info->compositor.reset(nullptr); // Then replace it with a new instance @@ -981,6 +996,9 @@ void reloadCompositors(CameraGroup *cgroup) viewport, compositor_path, options)); + + if (info->reloadCompositorCallback.valid()) + info->reloadCompositorCallback->postReloadCompositor(cgroup, info); } cgroup->_viewer->getViewerBase()->startThreading(); diff --git a/src/Viewer/CameraGroup.hxx b/src/Viewer/CameraGroup.hxx index 60d4faeb1..6e0bc58aa 100644 --- a/src/Viewer/CameraGroup.hxx +++ b/src/Viewer/CameraGroup.hxx @@ -51,6 +51,7 @@ namespace flightgear class CameraGroupListener; class GraphicsWindow; +class CameraGroup; /** A wrapper around osg::Camera that contains some extra information. */ @@ -76,7 +77,7 @@ struct CameraInfo : public osg::Referenced flags(flags_), physicalWidth(0), physicalHeight(0), bezelHeightTop(0), bezelHeightBottom(0), bezelWidthLeft(0), bezelWidthRight(0), - relativeCameraParent(0) { } + relativeCameraParent(0), reloadCompositorCallback(nullptr) { } /** The name as given in the config file. */ std::string name; @@ -120,6 +121,14 @@ struct CameraInfo : public osg::Referenced * Compositor path and should use the default one. */ std::string compositor_path; + + struct ReloadCompositorCallback : public virtual osg::Referenced + { + virtual void preReloadCompositor(CameraGroup *, CameraInfo *) = 0; + virtual void postReloadCompositor(CameraGroup *, CameraInfo *) = 0; + }; + osg::ref_ptr reloadCompositorCallback; + }; class CameraGroup : public osg::Referenced @@ -150,7 +159,11 @@ public: * @param cameraNode the property node. * @return a CameraInfo object for the camera. */ - void buildCamera(SGPropertyNode* cameraNode); + CameraInfo* buildCamera(SGPropertyNode* cameraNode); + /** Remove a camera from the camera group. + * @param info the camera info to remove. + */ + void removeCamera(CameraInfo *info); /** Create a camera from properties that will draw the GUI and add * it to the camera group. * @param cameraNode the property node. This can be 0, in which diff --git a/src/Viewer/VRManager.cxx b/src/Viewer/VRManager.cxx new file mode 100644 index 000000000..8417df65c --- /dev/null +++ b/src/Viewer/VRManager.cxx @@ -0,0 +1,221 @@ +// Copyright (C) 2021 James Hogan +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program 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 +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#include "VRManager.hxx" +#include "WindowBuilder.hxx" +#include "renderer.hxx" + +#include + +#include + +#include
+#include
+ +namespace flightgear +{ + +VRManager::VRManager() : + _reloadCompositorCallback(new ReloadCompositorCallback(this)), + _propXrLayersValidation("/sim/vr/openxr/layers/validation"), + _propXrExtensionsDepthInfo("/sim/vr/openxr/extensions/depth-info"), + _propXrRuntimeName("/sim/vr/openxr/runtime/name"), + _propXrSystemName("/sim/vr/openxr/system/name"), + _propStateString("/sim/vr/state-string"), + _propPresent("/sim/vr/present"), + _propRunning("/sim/vr/running"), + _propEnabled("/sim/vr/enabled"), + _propDepthInfo("/sim/vr/depth-info"), + _propValidationLayer("/sim/vr/validation-layer"), + _propMode("/sim/vr/mode"), + _propSwapchainMode("/sim/vr/swapchain-mode"), + _listenerEnabled(this, &osgXR::Manager::setEnabled), + _listenerDepthInfo(this, &VRManager::setDepthInfo), + _listenerValidationLayer(this, &VRManager::setValidationLayer), + _listenerMode(this, &VRManager::setVRMode), + _listenerSwapchainMode(this, &VRManager::setSwapchainMode) +{ + uint32_t fgVersion = (FLIGHTGEAR_MAJOR_VERSION << 16 | + FLIGHTGEAR_MINOR_VERSION << 8 | + FLIGHTGEAR_PATCH_VERSION); + _settings->setApp("FlightGear", fgVersion); + _settings->preferEnvBlendMode(osgXR::Settings::OPAQUE); + + // Hook into viewer, but don't enable VR just yet + osgViewer::View *view = globals->get_renderer()->getView(); + if (view) { + setViewer(globals->get_renderer()->getViewerBase()); + view->apply(this); + } + + syncReadOnlyProperties(); + + _propEnabled.node(true)->addChangeListener(&_listenerEnabled, true); + _propDepthInfo.node(true)->addChangeListener(&_listenerDepthInfo, true); + _propValidationLayer.node(true)->addChangeListener(&_listenerValidationLayer, true); + _propMode.node(true)->addChangeListener(&_listenerMode, true); + _propSwapchainMode.node(true)->addChangeListener(&_listenerSwapchainMode, true); +} + +VRManager *VRManager::instance() +{ + static osg::ref_ptr single = new VRManager; + return single; +} + +void VRManager::syncProperties() +{ + // If the state has changed, properties may need synchronising + if (checkAndResetStateChanged()) { + syncReadOnlyProperties(); + syncSettingProperties(); + } +} + +void VRManager::syncReadOnlyProperties() +{ + _propXrLayersValidation = hasValidationLayer(); + _propXrExtensionsDepthInfo = hasDepthInfoExtension(); + _propXrRuntimeName = getRuntimeName(); + _propXrSystemName = getSystemName(); + + _propStateString = getStateString(); + _propPresent = getPresent(); + _propRunning = isRunning(); +} + +void VRManager::syncSettingProperties() +{ + bool enabled = getEnabled(); + if (_propEnabled != enabled) + _propEnabled = enabled; +} + +void VRManager::setValidationLayer(bool validationLayer) +{ + _settings->setValidationLayer(validationLayer); + syncSettings(); +} + +void VRManager::setDepthInfo(bool depthInfo) +{ + _settings->setDepthInfo(depthInfo); + syncSettings(); +} + +void VRManager::setVRMode(const char * mode) +{ + osgXR::Settings::VRMode vrMode = osgXR::Settings::VRMODE_AUTOMATIC; + + if (strcmp(mode, "AUTOMATIC") == 0) { + vrMode = osgXR::Settings::VRMODE_AUTOMATIC; + } else if (strcmp(mode, "SLAVE_CAMERAS") == 0) { + vrMode = osgXR::Settings::VRMODE_SLAVE_CAMERAS; + } else if (strcmp(mode, "SCENE_VIEW") == 0) { + vrMode = osgXR::Settings::VRMODE_SCENE_VIEW; + } + + _settings->setVRMode(vrMode); + syncSettings(); +} + +void VRManager::setSwapchainMode(const char * mode) +{ + osgXR::Settings::SwapchainMode swapchainMode = osgXR::Settings::SWAPCHAIN_AUTOMATIC; + + if (strcmp(mode, "AUTOMATIC") == 0) { + swapchainMode = osgXR::Settings::SWAPCHAIN_AUTOMATIC; + } else if (strcmp(mode,"MULTIPLE") == 0) { + swapchainMode = osgXR::Settings::SWAPCHAIN_MULTIPLE; + } else if (strcmp(mode,"SINGLE") == 0) { + swapchainMode = osgXR::Settings::SWAPCHAIN_SINGLE; + } + + _settings->setSwapchainMode(swapchainMode); + syncSettings(); +} + +void VRManager::update() +{ + osgXR::Manager::update(); + syncProperties(); +} + +void VRManager::doCreateView(osgXR::View *xrView) +{ + // Restarted in osgXR::Manager::update() + _viewer->stopThreading(); + + // Construct a property tree for the camera + SGPropertyNode_ptr camNode = new SGPropertyNode; + WindowBuilder *windowBuilder = WindowBuilder::getWindowBuilder(); + setValue(camNode->getNode("window/name", true), + windowBuilder->getDefaultWindowName()); + + // Build a camera + CameraGroup *cgroup = CameraGroup::getDefault(); + CameraInfo *info = cgroup->buildCamera(camNode); + + // Notify osgXR about the new compositor's scene slave cameras + if (info) { + _camInfos[xrView] = info; + _xrViews[info] = xrView; + info->reloadCompositorCallback = _reloadCompositorCallback; + + postReloadCompositor(cgroup, info); + } +} + +void VRManager::doDestroyView(osgXR::View *xrView) +{ + // Restarted in osgXR::Manager::update() + _viewer->stopThreading(); + + CameraGroup *cgroup = CameraGroup::getDefault(); + auto it = _camInfos.find(xrView); + if (it != _camInfos.end()) { + osg::ref_ptr info = (*it).second; + _camInfos.erase(it); + + auto it2 = _xrViews.find(info.get()); + if (it2 != _xrViews.end()) + _xrViews.erase(it2); + + cgroup->removeCamera(info.get()); + } +} + +void VRManager::preReloadCompositor(CameraGroup *cgroup, CameraInfo *info) +{ + osgXR::View *xrView = _xrViews[info]; + + auto& passes = info->compositor->getPassList(); + for (auto& pass: passes) + if (pass->type == "scene") + xrView->removeSlave(pass->camera); +} + +void VRManager::postReloadCompositor(CameraGroup *cgroup, CameraInfo *info) +{ + osgXR::View *xrView = _xrViews[info]; + + auto& passes = info->compositor->getPassList(); + for (auto& pass: passes) + if (pass->type == "scene") + xrView->addSlave(pass->camera); +} + +} diff --git a/src/Viewer/VRManager.hxx b/src/Viewer/VRManager.hxx new file mode 100644 index 000000000..db97f682d --- /dev/null +++ b/src/Viewer/VRManager.hxx @@ -0,0 +1,158 @@ +// Copyright (C) 2021 James Hogan +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program 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 +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#ifndef VRMANAGER_HXX +#define VRMANAGER_HXX 1 + +#include + +#ifdef ENABLE_OSGXR + +#include +#include + +#include + +#include +#include + +#include "CameraGroup.hxx" + +#include + +namespace flightgear +{ + +class VRManager : public osgXR::Manager +{ + public: + + class ReloadCompositorCallback : public CameraInfo::ReloadCompositorCallback + { + public: + + ReloadCompositorCallback(VRManager *manager) : + _manager(manager) + { + }; + + virtual void preReloadCompositor(CameraGroup *cgroup, CameraInfo *info) + { + _manager->preReloadCompositor(cgroup, info); + } + + virtual void postReloadCompositor(CameraGroup *cgroup, CameraInfo *info) + { + _manager->postReloadCompositor(cgroup, info); + } + + protected: + + osg::observer_ptr _manager; + }; + + VRManager(); + + static VRManager *instance(); + + void syncProperties(); + void syncReadOnlyProperties(); + void syncSettingProperties(); + + // Settings + + void setValidationLayer(bool validationLayer); + void setDepthInfo(bool depthInfo); + + void setVRMode(const char * mode); + void setSwapchainMode(const char * mode); + + // osgXR::Manager overrides + + void update() override; + + void doCreateView(osgXR::View *xrView) override; + void doDestroyView(osgXR::View *xrView) override; + + void preReloadCompositor(CameraGroup *cgroup, CameraInfo *info); + void postReloadCompositor(CameraGroup *cgroup, CameraInfo *info); + + protected: + + typedef std::map> XRViewToCamInfo; + XRViewToCamInfo _camInfos; + + typedef std::map> CamInfoToXRView; + CamInfoToXRView _xrViews; + + osg::ref_ptr _reloadCompositorCallback; + + // Properties + + SGPropObjBool _propXrLayersValidation; + SGPropObjBool _propXrExtensionsDepthInfo; + SGPropObjString _propXrRuntimeName; + SGPropObjString _propXrSystemName; + + SGPropObjString _propStateString; + SGPropObjBool _propPresent; + SGPropObjBool _propRunning; + + SGPropObjBool _propEnabled; + SGPropObjBool _propDepthInfo; + SGPropObjBool _propValidationLayer; + SGPropObjString _propMode; + SGPropObjString _propSwapchainMode; + + // Property listeners + + template + class Listener : public SGPropertyChangeListener + { + public: + typedef void (VRManager::*SetterFn)(T v); + + Listener(VRManager *manager, SetterFn setter) : + _manager(manager), + _setter(setter) + { + } + + void valueChanged(SGPropertyNode *node) override + { + (_manager->*_setter)(node->template getValue()); + } + + protected: + + VRManager *_manager; + SetterFn _setter; + }; + typedef Listener ListenerBool; + typedef Listener ListenerString; + + ListenerBool _listenerEnabled; + ListenerBool _listenerDepthInfo; + ListenerBool _listenerValidationLayer; + ListenerString _listenerMode; + ListenerString _listenerSwapchainMode; +}; + +} + +#endif // ENABLE_OSGXR + +#endif diff --git a/src/Viewer/fg_os_osgviewer.cxx b/src/Viewer/fg_os_osgviewer.cxx index 600fbcf73..dae44a64d 100644 --- a/src/Viewer/fg_os_osgviewer.cxx +++ b/src/Viewer/fg_os_osgviewer.cxx @@ -52,6 +52,7 @@ #include "renderer.hxx" #include "CameraGroup.hxx" #include "FGEventHandler.hxx" +#include "VRManager.hxx" #include "WindowBuilder.hxx" #include "WindowSystemAdapter.hxx" #include
@@ -328,6 +329,12 @@ void fgOSResetProperties() fgTie("/sim/rendering/osg-displaysettings/double-buffer", displaySettings, &DisplaySettings::getDoubleBuffer, &DisplaySettings::setDoubleBuffer ); fgTie("/sim/rendering/osg-displaysettings/depth-buffer", displaySettings, &DisplaySettings::getDepthBuffer, &DisplaySettings::setDepthBuffer ); fgTie("/sim/rendering/osg-displaysettings/rgb", displaySettings, &DisplaySettings::getRGB, &DisplaySettings::setRGB ); + +#ifdef ENABLE_OSGXR + fgSetBool("/sim/vr/built", true); +#else + fgSetBool("/sim/vr/built", false); +#endif } @@ -389,6 +396,9 @@ int fgOSMainLoop() } } globals->get_renderer()->update(); +#ifdef ENABLE_OSGXR + VRManager::instance()->update(); +#endif viewer_base->frame( globals->get_sim_time_sec() ); } @@ -437,6 +447,9 @@ void fgOSCloseWindow() viewer_base->stopThreading(); } } +#ifdef ENABLE_OSGXR + VRManager::instance()->destroyAndWait(); +#endif FGScenery::resetPagerSingleton(); flightgear::addSentryBreadcrumb("fgOSCloseWindow, clearing camera group", "info"); flightgear::CameraGroup::setDefault(NULL);