1
0
Fork 0

VR: Implement minimal VR support

Implement support for virtual reality headsets via version 0.3 of the
osgXR[1] library I've been working on which uses OpenXR.

Add a new VRManager class based on osgXR::Manager to implement its
callbacks. When osgXR needs a new view created, we build a new camera in
the default camera group, and notify osgXR of each new "scene" typed
render pass. It also hooks into the CameraInfo's new compositor reload
callback to ensure osgXR is updated when the compositors are reloaded.

VR settings are controlled by properties, and new --enable-vr /
--disable-vr options are implemented to enable/disable VR at start.

This is enough to get basic VR for looking around the cockpit, but more
work will be required to support a desktop mirror of VR view, VR splash
screen, VR GUI, controller interaction, and correct positional sound.

[1] https://github.com/amalon/osgXR
This commit is contained in:
James Hogan 2021-07-17 23:19:14 +01:00
parent dcdda1044a
commit db4525abf9
No known key found for this signature in database
GPG key ID: 35CEE4862B1023F2
7 changed files with 406 additions and 0 deletions

View file

@ -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)]' \

View file

@ -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

View file

@ -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 },

View file

@ -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)

221
src/Viewer/VRManager.cxx Normal file
View file

@ -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 <osgXR/Settings>
#include <simgear/scene/viewer/CompositorPass.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
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<VRManager> 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<CameraInfo> 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);
}
}

158
src/Viewer/VRManager.hxx Normal file
View file

@ -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 <config.h>
#ifdef ENABLE_OSGXR
#include <osg/ref_ptr>
#include <osg/observer_ptr>
#include <osgXR/Manager>
#include <simgear/props/propertyObject.hxx>
#include <simgear/scene/viewer/CompositorPass.hxx>
#include "CameraGroup.hxx"
#include <map>
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<VRManager> _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<osgXR::View *, osg::ref_ptr<CameraInfo>> XRViewToCamInfo;
XRViewToCamInfo _camInfos;
typedef std::map<CameraInfo *, osg::ref_ptr<osgXR::View>> CamInfoToXRView;
CamInfoToXRView _xrViews;
osg::ref_ptr<ReloadCompositorCallback> _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 <typename T>
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<T>());
}
protected:
VRManager *_manager;
SetterFn _setter;
};
typedef Listener<bool> ListenerBool;
typedef Listener<const char *> ListenerString;
ListenerBool _listenerEnabled;
ListenerBool _listenerDepthInfo;
ListenerBool _listenerValidationLayer;
ListenerString _listenerMode;
ListenerString _listenerSwapchainMode;
};
}
#endif // ENABLE_OSGXR
#endif

View file

@ -52,6 +52,7 @@
#include "renderer.hxx"
#include "CameraGroup.hxx"
#include "FGEventHandler.hxx"
#include "VRManager.hxx"
#include "WindowBuilder.hxx"
#include "WindowSystemAdapter.hxx"
#include <Main/sentryIntegration.hxx>
@ -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);