first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions
+507
View File
@@ -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());
}
+84
View File
@@ -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
+242
View File
@@ -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();
}
+93
View File
@@ -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
+159
View File
@@ -0,0 +1,159 @@
# Dependencies
find_package(OpenGL REQUIRED)
find_package(OpenSceneGraph REQUIRED COMPONENTS osgViewer osgUtil)
find_package(OpenXR REQUIRED)
# Old OpenXR SDK versions don't find Threads but Threads::Threads is an
# INTERFACE link library of openxr_loader, so do so explicitly
if(OpenXR_VERSION VERSION_LESS 1.0.14)
find_package(Threads REQUIRED)
endif()
# Public header files
set(osgXR_HEADERS
include/osgXR/Action
include/osgXR/ActionSet
include/osgXR/CompositionLayer
include/osgXR/CompositionLayerQuad
include/osgXR/Export
include/osgXR/InteractionProfile
include/osgXR/Manager
include/osgXR/Mirror
include/osgXR/MirrorSettings
include/osgXR/OpenXRDisplay
include/osgXR/Settings
include/osgXR/SubImage
include/osgXR/Subaction
include/osgXR/Swapchain
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/Quirks.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
CompositionLayer.cpp
CompositionLayerQuad.cpp
FrameStore.cpp
InteractionProfile.cpp
Manager.cpp
Mirror.cpp
MirrorSettings.cpp
OpenXRDisplay.cpp
Settings.cpp
Subaction.cpp
Swapchain.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
)
# Enable compiler warnings
if(OSGXR_WARNINGS)
target_compile_options(osgXR PRIVATE
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
-Wall -pedantic
-Wextra -Wno-missing-field-initializers
-Wno-unused-parameter>
$<$<CXX_COMPILER_ID:MSVC>:
/W4>
)
endif()
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
)
+125
View File
@@ -0,0 +1,125 @@
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2022 James Hogan <james@albanarts.com>
#include "CompositionLayer.h"
#include "XRState.h"
using namespace osgXR;
// Internal API
CompositionLayer::Private::Private(XRState *state) :
_state(state),
_visible(true),
_order(1), // in front of perspective layer
_alphaMode(BLEND_NONE)
{
state->addCompositionLayer(this);
}
CompositionLayer::Private::~Private()
{
XRState *state = _state.get();
if (state)
state->removeCompositionLayer(this);
}
void CompositionLayer::Private::setVisible(bool visible)
{
_visible = visible;
}
bool CompositionLayer::Private::getVisible() const
{
return _visible;
}
void CompositionLayer::Private::setOrder(int order)
{
_order = order;
// FIXME reorder
}
int CompositionLayer::Private::getOrder() const
{
return _order;
}
void CompositionLayer::Private::setAlphaMode(AlphaMode mode)
{
_alphaMode = mode;
}
CompositionLayer::AlphaMode CompositionLayer::Private::getAlphaMode() const
{
return _alphaMode;
}
bool CompositionLayer::Private::writeCompositionLayer(OpenXR::Session *session,
OpenXR::CompositionLayer *layer,
bool disableAlpha) const
{
if (!_visible)
return false;
XrCompositionLayerFlags flags = 0;
AlphaMode alphaMode = disableAlpha ? BLEND_NONE : _alphaMode;
switch (alphaMode)
{
case BLEND_NONE:
break;
case BLEND_ALPHA_PREMULT:
flags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
break;
case BLEND_ALPHA_UNPREMULT:
flags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT |
XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
break;
}
layer->setLayerFlags(flags);
layer->setSpace(session->getLocalSpace());
return true;
}
// Public API
CompositionLayer::CompositionLayer(Private *priv) :
_private(priv)
{
}
CompositionLayer::~CompositionLayer()
{
}
void CompositionLayer::setVisible(bool visible)
{
_private->setVisible(visible);
}
bool CompositionLayer::getVisible() const
{
return _private->getVisible();
}
void CompositionLayer::setOrder(int order)
{
_private->setOrder(order);
}
int CompositionLayer::getOrder() const
{
return _private->getOrder();
}
void CompositionLayer::setAlphaMode(AlphaMode mode)
{
_private->setAlphaMode(mode);
}
CompositionLayer::AlphaMode CompositionLayer::getAlphaMode() const
{
return _private->getAlphaMode();
}
+75
View File
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2022 James Hogan <james@albanarts.com>
#ifndef OSGXR_COMPOSITION_LAYER
#define OSGXR_COMPOSITION_LAYER 1
#include <osgXR/CompositionLayer>
#include "OpenXR/Compositor.h"
#include "OpenXR/Session.h"
#include <osg/observer_ptr>
namespace osgXR {
class XRState;
class CompositionLayer::Private
{
public:
static Private *get(CompositionLayer *pub)
{
return pub->_private.get();
}
static const Private *get(const CompositionLayer *pub)
{
return pub->_private.get();
}
Private(XRState *state);
virtual ~Private();
void setVisible(bool visible);
bool getVisible() const;
void setOrder(int order);
int getOrder() const;
void setAlphaMode(AlphaMode mode);
AlphaMode getAlphaMode() const;
/// Write to composition layer
bool writeCompositionLayer(OpenXR::Session *session,
OpenXR::CompositionLayer *layer,
bool disableAlpha) const;
/// Setup composition layer with an OpenXR session
virtual bool setup(OpenXR::Session *session) = 0;
/// Add composition layers to the frame
virtual void endFrame(OpenXR::Session::Frame *frame) = 0;
/// Clean up composition layer before an OpenXR session is destroyed
virtual void cleanupSession() = 0;
/// For sorting operations
static bool compareOrder(Private *a, Private *b)
{
return a->_order < b->_order;
}
protected:
osg::observer_ptr<XRState> _state;
bool _visible;
int _order;
AlphaMode _alphaMode;
};
} // osgXR
#endif
+222
View File
@@ -0,0 +1,222 @@
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2022 James Hogan <james@albanarts.com>
#include <osgXR/CompositionLayerQuad>
#include <osgXR/Manager>
#include <osgXR/SubImage>
#include <osgXR/Swapchain>
#include "OpenXR/Compositor.h"
#include "CompositionLayer.h"
#include "Swapchain.h"
using namespace osgXR;
// Internal API
namespace osgXR {
class CompositionLayerPrivateQuad : public CompositionLayer::Private
{
public:
typedef CompositionLayerQuad::EyeVisibility EyeVisibility;
CompositionLayerPrivateQuad(XRState *state) :
CompositionLayer::Private(state),
_eyeVisibility(EyeVisibility::EYES_BOTH),
_orientation(0.0f, 0.0f, 0.0f, 1.0f),
_position(0.0f, 0.0f, -1.0f),
_size(1.0f, 1.0f)
{
}
~CompositionLayerPrivateQuad()
{
}
void setEyeVisibility(EyeVisibility eyes)
{
_eyeVisibility = eyes;
}
EyeVisibility getEyeVisibility() const
{
return _eyeVisibility;
}
void setSubImage(const SubImage &subImage)
{
_subImage = subImage;
}
const SubImage &getSubImage() const
{
return _subImage;
}
void setOrientation(const osg::Quat &quat)
{
_orientation = quat;
}
const osg::Quat &getOrientation() const
{
return _orientation;
}
void setPosition(const osg::Vec3f &pos)
{
_position = pos;
}
const osg::Vec3f &getPosition() const
{
return _position;
}
void setSize(const osg::Vec2f &size)
{
_size = size;
}
const osg::Vec2f &getSize() const
{
return _size;
}
bool setup(OpenXR::Session *session) override
{
Swapchain *swapchain = _subImage.getSwapchain();
if (swapchain)
return Swapchain::Private::get(swapchain)->setup(_state.get(), session);
return false;
}
bool writeCompositionLayerQuad(OpenXR::Session *session,
OpenXR::CompositionLayerQuad *layer) const
{
auto swapchain = Swapchain::Private::get(_subImage.getSwapchain());
bool ret = writeCompositionLayer(session, layer,
swapchain->getForcedAlpha() >= 1.0f);
if (!ret)
return ret;
_quadLayer->setEyeVisibility(static_cast<XrEyeVisibility>(_eyeVisibility));
_quadLayer->setSubImage(swapchain->convertSubImage(_subImage));
_quadLayer->setOrientation(_orientation);
_quadLayer->setPosition(_position);
_quadLayer->setSize(_size);
return true;
}
void endFrame(OpenXR::Session::Frame *frame) override
{
Swapchain *swapchain = _subImage.getSwapchain();
if (!swapchain)
return;
auto *swapchainPriv = Swapchain::Private::get(swapchain);
if (!swapchainPriv->valid())
return;
_quadLayer = new OpenXR::CompositionLayerQuad();
if (writeCompositionLayerQuad(frame->getSession(), _quadLayer))
frame->addLayer(_quadLayer.get());
}
void cleanupSession() override
{
Swapchain *swapchain = _subImage.getSwapchain();
if (swapchain)
Swapchain::Private::get(swapchain)->cleanupSession();
}
protected:
EyeVisibility _eyeVisibility;
SubImage _subImage;
osg::Quat _orientation;
osg::Vec3f _position;
osg::Vec2f _size;
osg::ref_ptr<OpenXR::CompositionLayerQuad> _quadLayer;
};
}
// Public API
CompositionLayerQuad::CompositionLayerQuad(Manager *manager) :
CompositionLayer(new CompositionLayerPrivateQuad(manager->_getXrState()))
{
}
CompositionLayerQuad::~CompositionLayerQuad()
{
}
void CompositionLayerQuad::setEyeVisibility(EyeVisibility eyes)
{
auto priv = static_cast<CompositionLayerPrivateQuad *>(Private::get(this));
priv->setEyeVisibility(eyes);
}
CompositionLayerQuad::EyeVisibility CompositionLayerQuad::getEyeVisibility() const
{
auto priv = static_cast<const CompositionLayerPrivateQuad *>(Private::get(this));
return priv->getEyeVisibility();
}
void CompositionLayerQuad::setSubImage(Swapchain *swapchain)
{
setSubImage((SubImage)swapchain);
}
void CompositionLayerQuad::setSubImage(const SubImage &subImage)
{
auto priv = static_cast<CompositionLayerPrivateQuad *>(Private::get(this));
priv->setSubImage(subImage);
}
const SubImage &CompositionLayerQuad::getSubImage() const
{
auto priv = static_cast<const CompositionLayerPrivateQuad *>(Private::get(this));
return priv->getSubImage();
}
void CompositionLayerQuad::setOrientation(const osg::Quat &quat)
{
auto priv = static_cast<CompositionLayerPrivateQuad *>(Private::get(this));
priv->setOrientation(quat);
}
const osg::Quat &CompositionLayerQuad::getOrientation() const
{
auto priv = static_cast<const CompositionLayerPrivateQuad *>(Private::get(this));
return priv->getOrientation();
}
void CompositionLayerQuad::setPosition(const osg::Vec3f &pos)
{
auto priv = static_cast<CompositionLayerPrivateQuad *>(Private::get(this));
priv->setPosition(pos);
}
const osg::Vec3f &CompositionLayerQuad::getPosition() const
{
auto priv = static_cast<const CompositionLayerPrivateQuad *>(Private::get(this));
return priv->getPosition();
}
void CompositionLayerQuad::setSize(const osg::Vec2f &size)
{
auto priv = static_cast<CompositionLayerPrivateQuad *>(Private::get(this));
priv->setSize(size);
}
const osg::Vec2f &CompositionLayerQuad::getSize() const
{
auto priv = static_cast<const CompositionLayerPrivateQuad *>(Private::get(this));
return priv->getSize();
}
+10
View File
@@ -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
+86
View File
@@ -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
+87
View File
@@ -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;
}
+63
View File
@@ -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
+106
View File
@@ -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);
}
+93
View File
@@ -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
+191
View File
@@ -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();
}
}
+141
View File
@@ -0,0 +1,141 @@
// 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;
int forceOn = osg::StateAttribute::ON | osg::StateAttribute::PROTECTED;
state->setMode(GL_LIGHTING, forceOff);
state->setMode(GL_DEPTH_TEST, forceOff);
state->setMode(GL_FRAMEBUFFER_SRGB, forceOn);
_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));
}
+12
View File
@@ -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)
{
}
+188
View File
@@ -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");
}
+371
View File
@@ -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
+35
View File
@@ -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");
}
}
+69
View File
@@ -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
+87
View File
@@ -0,0 +1,87 @@
// 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;
// CompositionLayerProjection
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);
}
// CompositionLayerQuad
void CompositionLayerQuad::setSubImage(const SwapchainGroup::SubImage &subImage)
{
subImage.getXrSubImage(&_layer.subImage);
}
const XrCompositionLayerBaseHeader *CompositionLayerQuad::getXr()
{
_layer.layerFlags = _layerFlags;
_layer.space = _space->getXrSpace();
return reinterpret_cast<const XrCompositionLayerBaseHeader*>(&_layer);
}
+165
View File
@@ -0,0 +1,165 @@
// 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;
};
class CompositionLayerQuad : public CompositionLayer
{
public:
CompositionLayerQuad() :
_layer{ XR_TYPE_COMPOSITION_LAYER_QUAD }
{
_layer.eyeVisibility = XR_EYE_VISIBILITY_BOTH;
_layer.subImage.swapchain = XR_NULL_HANDLE;
_layer.pose.orientation = { 0.0f, 0.0f, 0.0f, 1.0f };
}
virtual ~CompositionLayerQuad()
{
}
inline XrEyeVisibility getEyeVisibility() const
{
return _layer.eyeVisibility;
}
inline void setEyeVisibility(XrEyeVisibility eyeVisibility)
{
_layer.eyeVisibility = eyeVisibility;
}
void setSubImage(const SwapchainGroup::SubImage &subImage);
inline osg::Quat getOrientation() const
{
return osg::Quat(_layer.pose.orientation.x,
_layer.pose.orientation.y,
_layer.pose.orientation.z,
_layer.pose.orientation.w);
}
inline void setOrientation(const osg::Quat &quat)
{
_layer.pose.orientation.x = quat.x();
_layer.pose.orientation.y = quat.y();
_layer.pose.orientation.z = quat.z();
_layer.pose.orientation.w = quat.w();
}
inline osg::Vec3f getPosition() const
{
return osg::Vec3f(_layer.pose.position.x,
_layer.pose.position.y,
_layer.pose.position.z);
}
inline void setPosition(const osg::Vec3f &pos)
{
_layer.pose.position.x = pos.x();
_layer.pose.position.y = pos.y();
_layer.pose.position.z = pos.z();
}
inline osg::Vec2f getSize() const
{
return osg::Vec2f(_layer.size.width,
_layer.size.height);
}
inline void setSize(const osg::Vec2f &size)
{
_layer.size.width = size.x();
_layer.size.height = size.y();
}
const XrCompositionLayerBaseHeader *getXr() override;
protected:
mutable XrCompositionLayerQuad _layer;
};
} // osgXR::OpenXR
} // osgXR
#endif
+80
View File
@@ -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
+182
View File
@@ -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)
{
}
+75
View File
@@ -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
+69
View File
@@ -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;
}
+48
View File
@@ -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
+14
View File
@@ -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();
}
+30
View File
@@ -0,0 +1,30 @@
// 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>
#include <unknwn.h>
#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
+40
View File
@@ -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();
}
+29
View File
@@ -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
+405
View File
@@ -0,0 +1,405 @@
// 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;
switch (res)
{
case XR_ERROR_RUNTIME_UNAVAILABLE:
case XR_ERROR_RUNTIME_FAILURE: // Monado returns this when not running
return INIT_LATER;
default:
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;
_quirks.probe(this);
}
// 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);
}
}
+193
View File
@@ -0,0 +1,193 @@
// 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 "Quirks.h"
#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;
}
inline XrVersion getRuntimeVersion() const
{
return _properties.runtimeVersion;
}
inline bool getQuirk(Quirk quirk) const
{
return _quirks[quirk];
}
// 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;
// Quirks
Quirks _quirks;
// Systems
mutable std::vector<System *> _systems;
// Sessions
std::map<XrSession, Session *> _sessions;
};
} // osgXR::OpenXR
} // osgXR
#endif
+59
View File
@@ -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");
}
+77
View File
@@ -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
+41
View File
@@ -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();
}
+80
View File
@@ -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
+119
View File
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2021 James Hogan <james@albanarts.com>
#include "Instance.h"
#include "Quirks.h"
#include <cstdlib>
#include <cstring>
#ifdef OSGXR_USE_X11
#define USING_X11 1
#else
#define USING_X11 0
#endif
using namespace osgXR::OpenXR;
void Quirks::probe(Instance *instance)
{
static struct {
Quirk quirk;
const char *envName;
bool condition;
const char *runtimeMatch;
XrVersion runtimeVersionMin;
XrVersion runtimeVersionMax;
const char *description;
} quirkInfo[] = {
#define QUIRK(NAME, COND, RUNTIME, VMIN, VMAX, LINK) \
{ NAME, "OSGXR_"#NAME, COND, RUNTIME, VMIN, VMAX, \
#NAME LINK }
#define MIN_XR_VERSION XR_MAKE_VERSION(0, 0, 0)
#define MAX_XR_VERSION XR_MAKE_VERSION(-1, -1, -1)
#define MATCH_MONADO "Monado"
#define MATCH_STEAMVR "SteamVR"
// As of 2021-12-16 Monado expects the GL context to be current.
// See https://gitlab.freedesktop.org/monado/monado/-/issues/145
// Fixed by https://gitlab.freedesktop.org/monado/monado/-/merge_requests/1216
QUIRK(QUIRK_GL_CONTEXT_IGNORED,
USING_X11,
MATCH_MONADO, MIN_XR_VERSION, XR_MAKE_VERSION(21, 0, 0),
" (https://gitlab.freedesktop.org/monado/monado/-/issues/145)"),
// Prior to 1.16.4 linux_v1.14 switched context but didn't restore.
// The SteamVR runtimeVersion is unfortunately fairly useless here.
QUIRK(QUIRK_GL_CONTEXT_CHANGED,
USING_X11,
MATCH_STEAMVR, MIN_XR_VERSION, XR_MAKE_VERSION(0, 1, 0),
""),
// Since SteamVR 1.16.4 the GL context is cleared by various calls.
// The SteamVR runtimeVersion is unfortunately fairly useless here.
QUIRK(QUIRK_GL_CONTEXT_CLEARED,
USING_X11,
MATCH_STEAMVR, MIN_XR_VERSION, MAX_XR_VERSION,
" (https://github.com/ValveSoftware/SteamVR-for-Linux/issues/421)"),
// Since SteamVR 1.15.x apps hang during xrDestroyInstance.
QUIRK(QUIRK_AVOID_DESTROY_INSTANCE,
USING_X11,
MATCH_STEAMVR, MIN_XR_VERSION, MAX_XR_VERSION,
" (https://github.com/ValveSoftware/SteamVR-for-Linux/issues/422)"),
#undef QUIRK
};
const char *runtime = instance->getRuntimeName();
XrVersion version = instance->getRuntimeVersion();
// Clear all quirks
reset();
// Probe each quirk
for (auto &quirk: quirkInfo)
{
const char *env = getenv(quirk.envName);
if (env)
{
if (!strncmp(env, "0", 2))
{
set(quirk.quirk, false);
}
else if (!strncmp(env, "1", 2))
{
set(quirk.quirk, true);
}
else
{
env = nullptr;
OSG_WARN << "osgXR: Unknown value for env \""
<< quirk.envName << "\", ignored" << std::endl;
}
}
// Probe the runtime name and version
if (!env &&
quirk.condition &&
!strncmp(runtime, quirk.runtimeMatch, strlen(quirk.runtimeMatch)) &&
version >= quirk.runtimeVersionMin &&
version <= quirk.runtimeVersionMax)
{
set(quirk.quirk, true);
}
}
// Print to log any enabled quirks
if (any())
{
OSG_WARN << "osgXR: OpenXR Runtime: \"" << runtime
<< "\" version " << XR_VERSION_MAJOR(version)
<< "." << XR_VERSION_MINOR(version)
<< "." << XR_VERSION_PATCH(version) << std::endl;
for (auto &quirk: quirkInfo)
if (test(quirk.quirk))
OSG_WARN << "osgXR: Enabling " << quirk.description << std::endl;
}
}
+71
View File
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2021 James Hogan <james@albanarts.com>
#ifndef OSGXR_OPENXR_QUIRKS
#define OSGXR_OPENXR_QUIRKS 1
#include <bitset>
namespace osgXR {
namespace OpenXR {
class Instance;
typedef enum Quirk {
/**
* This quirk indicates that the GLX context may be assumed to be current by
* certain XR calls.
* The affected calls are:
* - xrCreateSession
* - xrCreateSwapchain
*/
QUIRK_GL_CONTEXT_IGNORED = 0,
/**
* This quirk indicates that the GLX context may be switched but not
* restored by certain XR calls.
* The affected calls are:
* - xrCreateSwapchain
*/
QUIRK_GL_CONTEXT_CHANGED,
/**
* This quirk indicates that the GLX context may be unconditionally cleared
* by various XR calls.
* The affected calls are:
* - xrCreateSwapchain
* - xrAcquireSwapchainImage
* - xrWaitSwapchainImage
* - xrReleaseSwapchainImage
* - xrEndFrame
*/
QUIRK_GL_CONTEXT_CLEARED,
/**
* This quirk indicates that the app should avoid destroying the XR instance
* to avoid hangs.
*/
QUIRK_AVOID_DESTROY_INSTANCE,
QUIRK_MAX
} Quirk;
/**
* Represents a set of OpenXR runtime quirks which require workarounds.
*/
class Quirks : public std::bitset<QUIRK_MAX>
{
public:
/**
* Probe the OpenXR instance to see which quirks are required.
*/
void probe(Instance *instance);
};
} // osgXR::OpenXR
} // osgXR
#endif
+527
View File
@@ -0,0 +1,527 @@
// 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 switchContext = shouldSwitchContext();
if (switchContext)
makeCurrent();
if (check(xrCreateSession(getXrInstance(), &createInfo, &_session),
"Failed to create OpenXR session"))
{
_instance->registerSession(this);
}
if (switchContext)
releaseContext();
}
Session::~Session()
{
releaseGLObjects();
}
void Session::releaseGLObjects(osg::State *state)
{
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");
_session = XR_NULL_HANDLE;
_running = false;
}
}
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()
{
if (!valid())
return false;
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()
{
if (_instance->lost())
return nullptr;
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 restoreContext = _session->shouldRestoreContext();
bool ret = check(xrEndFrame(_session->getXrSession(), &frameEndInfo),
"Failed to end OpenXR frame");
if (restoreContext)
_session->makeCurrent();
return ret;
}
+471
View File
@@ -0,0 +1,471 @@
// 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();
// GL context must not be bound in another thread
void releaseGLObjects(osg::State *state = nullptr);
// 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
/**
* Check whether the session's GLX context is current.
* This is intended for internal use only by the functions below.
* @return whether the session's GLX context is current.
*/
bool checkCurrent() const;
/**
* Make the GLX context current.
* This makes the GLX context current to workaround broken XR runtimes.
* It should be used to ensure the context is current before XR calls
* affected by QUIRK_GL_CONTEXT_IGNORED if shouldSwitchContext()
* (because the XR runtime will fail to do so), and to restore the
* context after XR calls affected by QUIRK_GL_CONTEXT_CLEARED if
* shouldRestoreContext() or getRestoreAction() == CONTEXT_RESTORE
* (because the XR runtime will have explicitly released the context).
*/
void makeCurrent() const;
/**
* Release the GLX context so that none is current.
* This makes no GLX context current to workaround broken XR runtimes.
* It should be used release the switched context after XR calls
* affected by QUIRK_GL_CONTEXT_IGNORED if shouldSwitchContext()
* (because we had to call makeCurrent() before the call), and after XR
* calls affected by QUIRK_GL_CONTEXT_CHANGED if getRestoreAction() ==
* CONTEXT_RELEASE (because the XR runtime failed to do so).
*/
void releaseContext() const;
/// Action to perform before or after XR call due to quirks.
typedef enum ContextAction {
/// No action is necessary.
CONTEXT_IGNORE = 0,
/// The GLX context should be restored.
CONTEXT_RESTORE,
/**
* The GLX context should be released leaving no context current.
* It is assumed that no other context will need restoring in its
* place.
*/
CONTEXT_RELEASE
} ContextAction;
/**
* Find whether for an XR call affected by QUIRK_GL_CONTEXT_IGNORED it
* is necessary to switch the context prior to the XR call with
* makeCurrent(), and also release it afterwards with releaseContext.
* @return true if the context should be switched and released,
* false otherwise.
*/
bool shouldSwitchContext() const
{
return _instance->getQuirk(QUIRK_GL_CONTEXT_IGNORED) &&
!checkCurrent();
}
/**
* Find whether for an XR call affected by QUIRK_GL_CONTEXT_CLEARED it
* is necessary to restore the context after the XR call.
* @return true if the context should be restored after,
* false otherwise.
*/
bool shouldRestoreContext() const
{
return _instance->getQuirk(QUIRK_GL_CONTEXT_CLEARED) &&
checkCurrent();
}
/**
* Find what GL context action to perform after an XR call affected by
* QUIRK_GL_CONTEXT_CLEARED or QUIRK_GL_CONTEXT_CHANGED.
* @return CONTEXT_RESTORE if the runtime may clear the context and it
* will need restoring.
* CONTEXT_RELEASE if the runtime may change the context and
* fail to release it.
* CONTEXT_IGNORE otherwise.
*/
ContextAction getRestoreAction() const
{
bool cleared = _instance->getQuirk(QUIRK_GL_CONTEXT_CLEARED);
bool changed = _instance->getQuirk(QUIRK_GL_CONTEXT_CHANGED);
if (cleared || changed)
{
bool current = checkCurrent();
if (cleared && current)
return CONTEXT_RESTORE;
if (changed && !current)
return CONTEXT_RELEASE;
}
return CONTEXT_IGNORE;
}
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 Session *getSession()
{
return _session;
}
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
+94
View File
@@ -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() && _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;
}
+126
View File
@@ -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
+162
View File
@@ -0,0 +1,162 @@
// 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 switchContext = _session->shouldSwitchContext();
auto restoreAction = _session->getRestoreAction();
if (switchContext)
_session->makeCurrent();
// GL context must not be bound in another thread
check(xrCreateSwapchain(getXrSession(), &createInfo, &_swapchain),
"Failed to create OpenXR swapchain");
if (restoreAction == Session::CONTEXT_RESTORE)
_session->makeCurrent();
else if (switchContext || restoreAction == Session::CONTEXT_RELEASE)
_session->releaseContext();
}
Swapchain::~Swapchain()
{
if (_session->valid() && 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 restoreContext = _session->shouldRestoreContext();
// GL context must not be bound in another thread
if (check(xrAcquireSwapchainImage(_swapchain, nullptr, &imageIndex),
"Failed to acquire swapchain image"))
{
if (restoreContext)
_session->makeCurrent();
return imageIndex;
}
if (restoreContext)
_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 restoreContext = _session->shouldRestoreContext();
// GL context must not be bound in another thread
bool ret = check(xrWaitSwapchainImage(_swapchain, &waitInfo),
"Failed to wait for swapchain image");
if (restoreContext)
_session->makeCurrent();
return ret;
}
void Swapchain::releaseImage() const
{
// Release the swapchain image
bool restoreContext = _session->shouldRestoreContext();
// GL context must not be bound in another thread
check(xrReleaseSwapchainImage(_swapchain, nullptr),
"Failed to release OpenXR swapchain image");
if (restoreContext)
_session->makeCurrent();
}
+119
View File
@@ -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
+55
View File
@@ -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();
}
+115
View File
@@ -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
+114
View File
@@ -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
+122
View File
@@ -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;
}
+221
View File
@@ -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
+42
View File
@@ -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));
}
+85
View File
@@ -0,0 +1,85 @@
// 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),
_preferredRGBEncodingMask(0),
_allowedRGBEncodingMask(0),
_preferredDepthEncodingMask(0),
_allowedDepthEncodingMask(0),
_rgbBits(-1),
_alphaBits(-1),
_depthBits(-1),
_stencilBits(-1),
_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 (_preferredRGBEncodingMask != other._preferredRGBEncodingMask ||
_allowedRGBEncodingMask != other._allowedRGBEncodingMask)
ret |= DIFF_RGB_ENCODING;
if (_preferredDepthEncodingMask != other._preferredDepthEncodingMask ||
_allowedDepthEncodingMask != other._allowedDepthEncodingMask)
ret |= DIFF_DEPTH_ENCODING;
if (_rgbBits != other._rgbBits)
ret |= DIFF_RGB_BITS;
if (_alphaBits != other._alphaBits)
ret |= DIFF_ALPHA_BITS;
if (_depthBits != other._depthBits)
ret |= DIFF_DEPTH_BITS;
if (_stencilBits != other._stencilBits)
ret |= DIFF_STENCIL_BITS;
if (_mirrorSettings != other._mirrorSettings)
ret |= DIFF_MIRROR;
if (_unitsPerMeter != other._unitsPerMeter)
ret |= DIFF_SCALE;
return ret;
}
+109
View File
@@ -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
}
+75
View File
@@ -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
+424
View File
@@ -0,0 +1,424 @@
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2022 James Hogan <james@albanarts.com>
#include "Swapchain.h"
#include "OpenXR/System.h"
#include "XRState.h"
#include <osg/Notify>
#include <osg/observer_ptr>
#include <osgViewer/Renderer>
#include <memory>
#include <sstream>
using namespace osgXR;
namespace {
// Draw callbacks
class InitialDrawCallback : public osg::Camera::DrawCallback
{
public:
InitialDrawCallback(std::shared_ptr<Swapchain::Private> &swapchain) :
_swapchain(swapchain)
{
}
void operator()(osg::RenderInfo& renderInfo) const override
{
auto swapchain = _swapchain.lock();
if (swapchain)
swapchain->initialDrawCallback(renderInfo);
}
protected:
std::weak_ptr<Swapchain::Private> _swapchain;
};
class PreDrawCallback : public osg::Camera::DrawCallback
{
public:
PreDrawCallback(std::shared_ptr<Swapchain::Private> &swapchain) :
_swapchain(swapchain)
{
swapchain->incNumDrawPasses();
}
~PreDrawCallback()
{
auto swapchain = _swapchain.lock();
if (swapchain)
swapchain->decNumDrawPasses();
}
void operator()(osg::RenderInfo& renderInfo) const override
{
auto swapchain = _swapchain.lock();
if (swapchain)
swapchain->preDrawCallback(renderInfo);
}
protected:
std::weak_ptr<Swapchain::Private> _swapchain;
};
class PostDrawCallback : public osg::Camera::DrawCallback
{
public:
PostDrawCallback(std::shared_ptr<Swapchain::Private> &swapchain) :
_swapchain(swapchain)
{
}
void operator()(osg::RenderInfo& renderInfo) const override
{
auto swapchain = _swapchain.lock();
if (swapchain)
swapchain->postDrawCallback(renderInfo);
}
protected:
std::weak_ptr<Swapchain::Private> _swapchain;
};
}
// Internal API
Swapchain::Private::Private(uint32_t width, uint32_t height) :
_preferredRGBEncodingMask(0),
_allowedRGBEncodingMask(0),
_rgbBits(10), // 8-bits is unlikely to be sufficient for linear encodings
_alphaBits(0), // Alpha channel not required by default
_width(width),
_height(height),
_forcedAlpha(-1.0f),
_numDrawPasses(0),
_updated(true)
{
}
Swapchain::Private::~Private()
{
}
void Swapchain::Private::attachToCamera(std::shared_ptr<Private> &self,
osg::Camera *camera)
{
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER);
camera->setInitialDrawCallback(new InitialDrawCallback(self));
camera->setPreDrawCallback(new PreDrawCallback(self));
camera->setFinalDrawCallback(new PostDrawCallback(self));
// FIXME Do all cameras inherit the main display settings... eeek!
//camera->setDisplaySettings(osg::DisplaySettings::instance());
if (_swapchain.valid())
_swapchain->incNumDrawPasses(1); // FIXME HACK depends on where node is!
}
void Swapchain::Private::attachToMirror(std::shared_ptr<Private> &self,
osg::StateSet *stateSet)
{
_stateSets.push_back(stateSet);
}
void Swapchain::Private::preferRGBEncoding(Encoding encoding)
{
uint32_t mask = (1u << (unsigned int)encoding);
_preferredRGBEncodingMask |= mask;
_allowedRGBEncodingMask |= mask;
}
void Swapchain::Private::allowRGBEncoding(Encoding encoding)
{
uint32_t mask = (1u << (unsigned int)encoding);
_allowedRGBEncodingMask |= mask;
}
void Swapchain::Private::setRGBBits(unsigned int rgbBits)
{
_rgbBits = rgbBits;
}
unsigned int Swapchain::Private::getRGBBits() const
{
return _rgbBits;
}
void Swapchain::Private::setAlphaBits(unsigned int alphaBits)
{
_alphaBits = alphaBits;
}
unsigned int Swapchain::Private::getAlphaBits() const
{
return _alphaBits;
}
void Swapchain::Private::setWidth(uint32_t width)
{
if (width != _width)
_updated = true;
_width = width;
}
uint32_t Swapchain::Private::getWidth() const
{
return _width;
}
void Swapchain::Private::setHeight(uint32_t height)
{
if (height != _height)
_updated = true;
_height = height;
}
uint32_t Swapchain::Private::getHeight() const
{
return _height;
}
void Swapchain::Private::setForcedAlpha(float alpha)
{
if (alpha < 0.0f)
alpha = 0.0f;
if (alpha > 1.0f)
alpha = 1.0f;
_forcedAlpha = alpha;
}
void Swapchain::Private::disableForcedAlpha()
{
_forcedAlpha = -1.0f;
}
float Swapchain::Private::getForcedAlpha() const
{
return _forcedAlpha;
}
bool Swapchain::Private::setup(XRState *state, OpenXR::Session *session)
{
XRState *oldState = _state.get();
if (oldState)
{
if (oldState != state)
{
OSG_WARN << "Swapchain XRState conflict" << std::endl;
return false;
}
if (!_updated)
return true;
}
OpenXR::System::ViewConfiguration::View view(_width, _height);
int64_t rgbaFormat = state->chooseRGBAFormat(_rgbBits, _alphaBits,
_preferredRGBEncodingMask,
_allowedRGBEncodingMask);
if (!rgbaFormat)
{
std::stringstream formats;
formats << std::hex;
for (int64_t format: session->getSwapchainFormats())
formats << " 0x" << format;
OSG_WARN << "Swapchain setup: No supported swapchain format found in ["
<< formats.str() << " ]" << std::endl;
return false;
}
_state = state;
_updated = false;
_session = session;
_swapchain = new XRState::XRSwapchain(state, session,
view, rgbaFormat,
0, GL_DEPTH_COMPONENT16);
_swapchain->setForcedAlpha(_forcedAlpha);
_swapchain->incNumDrawPasses(_numDrawPasses);
return true;
}
bool Swapchain::Private::sync()
{
if (_updated && _session.valid())
return setup(_state.get(), _session.get());
return true;
}
void Swapchain::Private::cleanupSession()
{
_swapchain = nullptr;
_session = nullptr;
_state = nullptr;
}
bool Swapchain::Private::valid() const
{
return _swapchain.valid();
}
void Swapchain::Private::initialDrawCallback(osg::RenderInfo &renderInfo)
{
if (_swapchain.valid())
{
// FIXME this isn't the ideal place, but it'll probably do in practice
// due to lack of concurrency
sync();
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);
}
_state->startRendering(renderInfo.getState()->getFrameStamp());
}
}
void Swapchain::Private::preDrawCallback(osg::RenderInfo &renderInfo)
{
if (_swapchain.valid())
_swapchain->preDrawCallback(renderInfo);
}
void Swapchain::Private::postDrawCallback(osg::RenderInfo &renderInfo)
{
if (_swapchain.valid())
{
_swapchain->setForcedAlpha(_forcedAlpha);
_swapchain->postDrawCallback(renderInfo);
const osg::FrameStamp *stamp = renderInfo.getState()->getFrameStamp();
auto texture = _swapchain->getOsgTexture(stamp);
if (texture.valid())
{
for (auto it = _stateSets.begin(); it != _stateSets.end(); ++it)
{
osg::ref_ptr<osg::StateSet> stateSet = *it;
if (stateSet.valid())
// update state set's texture object
stateSet->setTextureAttributeAndModes(0, texture);
else
// clean up after stale state sets
it = _stateSets.erase(it);
}
}
// FIXME somewhere we should remove or reset the texture:
// stateSet->removeTextureAttribute(0, osg::StateAttribute::Type::TEXTURE);
}
}
OpenXR::SwapchainGroupSubImage Swapchain::Private::convertSubImage(const SubImage &subImage) const
{
OpenXR::System::ViewConfiguration::View::Viewport vp = {};
vp.x = subImage.getX();
vp.y = subImage.getY();
vp.width = subImage.getWidth();
if (!vp.width)
vp.width = _width;
vp.height = subImage.getHeight();
if (!vp.height)
vp.height = _height;
return OpenXR::SwapchainGroupSubImage(_swapchain.get(), vp);
}
// Public API
Swapchain::Swapchain(uint32_t width, uint32_t height) :
_private(new Private(width, height))
{
}
Swapchain::~Swapchain()
{
}
void Swapchain::attachToCamera(osg::Camera *camera)
{
_private->attachToCamera(_private, camera);
}
void Swapchain::attachToMirror(osg::StateSet *stateSet)
{
_private->attachToMirror(_private, stateSet);
}
void Swapchain::preferRGBEncoding(Swapchain::Encoding encoding)
{
_private->preferRGBEncoding(encoding);
}
void Swapchain::allowRGBEncoding(Swapchain::Encoding encoding)
{
_private->allowRGBEncoding(encoding);
}
void Swapchain::setRGBBits(unsigned int rgbBits)
{
_private->setRGBBits(rgbBits);
}
unsigned int Swapchain::getRGBBits() const
{
return _private->getRGBBits();
}
void Swapchain::setAlphaBits(unsigned int alphaBits)
{
_private->setAlphaBits(alphaBits);
}
unsigned int Swapchain::getAlphaBits() const
{
return _private->getAlphaBits();
}
void Swapchain::setSize(uint32_t width, uint32_t height)
{
_private->setWidth(width);
_private->setHeight(height);
}
void Swapchain::setWidth(uint32_t width)
{
_private->setWidth(width);
}
uint32_t Swapchain::getWidth() const
{
return _private->getWidth();
}
void Swapchain::setHeight(uint32_t height)
{
_private->setHeight(height);
}
uint32_t Swapchain::getHeight() const
{
return _private->getHeight();
}
void Swapchain::setForcedAlpha(float alpha)
{
_private->setForcedAlpha(alpha);
}
void Swapchain::disableForcedAlpha()
{
_private->disableForcedAlpha();
}
float Swapchain::getForcedAlpha() const
{
return _private->getForcedAlpha();
}
+115
View File
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2022 James Hogan <james@albanarts.com>
#ifndef OSGXR_SWAPCHAIN
#define OSGXR_SWAPCHAIN 1
#include <osgXR/SubImage>
#include <osgXR/Swapchain>
#include "OpenXR/Session.h"
#include "OpenXR/SwapchainGroupSubImage.h"
#include "XRState.h"
#include <osg/Camera>
#include <osg/observer_ptr>
namespace osgXR {
class XRState;
class Swapchain::Private
{
public:
static Private *get(Swapchain *pub)
{
return pub->_private.get();
}
Private(uint32_t width, uint32_t height);
virtual ~Private();
void attachToCamera(std::shared_ptr<Private> &self,
osg::Camera *camera);
void attachToMirror(std::shared_ptr<Private> &self,
osg::StateSet *stateSet);
// Accessors
void preferRGBEncoding(Encoding encoding);
void allowRGBEncoding(Encoding encoding);
void setRGBBits(unsigned int rgbBits);
unsigned int getRGBBits() const;
void setAlphaBits(unsigned int alphaBits);
unsigned int getAlphaBits() const;
void setWidth(uint32_t width);
uint32_t getWidth() const;
void setHeight(uint32_t height);
uint32_t getHeight() const;
void setForcedAlpha(float alpha);
void disableForcedAlpha();
float getForcedAlpha() const;
// Internal API
/// Setup swapchain with an OpenXR session
bool setup(XRState *state, OpenXR::Session *session);
/// Synchronise any app changes, such as resizes
bool sync();
/// Clean up swapchain before an OpenXR session is destroyed
void cleanupSession();
/// Find whether the swapchain is valid for use.
bool valid() const;
void initialDrawCallback(osg::RenderInfo &renderInfo);
void preDrawCallback(osg::RenderInfo &renderInfo);
void postDrawCallback(osg::RenderInfo &renderInfo);
void incNumDrawPasses()
{
++_numDrawPasses;
}
void decNumDrawPasses()
{
--_numDrawPasses;
}
OpenXR::SwapchainGroupSubImage convertSubImage(const SubImage &subImage) const;
protected:
// Format requirements
uint32_t _preferredRGBEncodingMask;
uint32_t _allowedRGBEncodingMask;
int _rgbBits; // for linear RGB formats, per channel
int _alphaBits;
// Dimention requirements
uint32_t _width;
uint32_t _height;
// Forced alpha
float _forcedAlpha;
unsigned int _numDrawPasses;
bool _updated;
// State sets to update
std::list<osg::observer_ptr<osg::StateSet>> _stateSets;
// State
osg::observer_ptr<XRState> _state;
osg::observer_ptr<OpenXR::Session> _session;
osg::ref_ptr<XRState::XRSwapchain> _swapchain;
};
} // osgXR
#endif
+12
View File
@@ -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
+16
View File
@@ -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()
{
}
+151
View File
@@ -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;
}
}
+52
View File
@@ -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
+35
View File
@@ -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;
}
}
}
+38
View File
@@ -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
+1937
View File
File diff suppressed because it is too large Load Diff
+659
View File
@@ -0,0 +1,659 @@
// 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/CompositionLayer>
#include <osgXR/InteractionProfile>
#include <osgXR/Settings>
#include <osgXR/Subaction>
#include <osgXR/View>
#include <list>
#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 chosenRGBAFormat,
int64_t chosenDepthFormat,
GLenum fallbackDepthFormat);
// GL context must be current (for XRFramebuffer)
virtual ~XRSwapchain();
void setForcedAlpha(float alpha = -1.0f)
{
_forcedAlpha = alpha;
}
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;
float _forcedAlpha;
/// 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
{
if (_instance.valid() && _instance->getQuirk(OpenXR::QUIRK_AVOID_DESTROY_INSTANCE))
return _probing ? VRSTATE_SYSTEM : VRSTATE_DISABLED;
return 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();
/// Add a composition layer
void addCompositionLayer(CompositionLayer::Private *layer);
/// Remove a composition layer
void removeCompositionLayer(CompositionLayer::Private *layer);
/// 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 releaseGLObjects(osg::State *state);
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);
}
/**
* Choose an RGBA swapchain format.
* @param bestRGBBits Desired number of combined RGB bits.
* @param bestAlphaBits Desired number of alpha bits.
* @param preferredRGBEncodingMask Mask of preferred RGB encodings (see
* Settings::Encoding).
* @param allowedRGBEncodingMask Mask of allowed RGB encodings (see
* Settings::Encoding).
* @return The chosen OpenGL swapchain format.
*/
int64_t chooseRGBAFormat(unsigned int bestRGBBits,
unsigned int bestAlphaBits,
uint32_t preferredRGBEncodingMask,
uint32_t allowedRGBEncodingMask) const;
/**
* Choose a fallback depth / stencil format for use if the OpenXR
* runtime doesn't support depth swapchains.
* @param bestDepthBits Desired number of depth bits.
* @param bestStencilBits Desired number of stencil bits.
* @param preferredDepthEncodingMask Mask of preferred depth encodings
* (see Settings::Encoding).
* @param allowedDepthEncodingMask Mask of allowed depth encodings
* (see Settings::Encoding).
* @return The chosen fallback OpenGL depth / stencil format.
*/
GLenum chooseFallbackDepthFormat(unsigned int bestDepthBits,
unsigned int bestStencilBits,
uint32_t preferredDepthEncodingMask,
uint32_t allowedDepthEncodingMask) const;
/**
* Choose a depth / stencil swapchain format for submission to OpenXR.
* @param bestDepthBits Desired number of depth bits.
* @param bestStencilBits Desired number of stencil bits.
* @param preferredDepthEncodingMask Mask of preferred depth encodings
* (see Settings::Encoding).
* @param allowedDepthEncodingMask Mask of allowed depth encodings
* (see Settings::Encoding).
* @return The chosen OpenGL depth / stencil swapchain format.
*/
int64_t chooseDepthFormat(unsigned int bestDepthBits,
unsigned int bestStencilBits,
uint32_t preferredDepthEncodingMask,
uint32_t allowedDepthEncodingMask) const;
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,
GLenum fallbackDepthFormat = 0);
// Set up a swapchain for each view
bool setupMultipleSwapchains(int64_t format, int64_t depthFormat = 0,
GLenum fallbackDepthFormat = 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;
// Composition layers
bool _compositionLayersUpdated;
std::list<CompositionLayer::Private *> _compositionLayers;
/// 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
+200
View File
@@ -0,0 +1,200 @@
// 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);
}
void releaseGLObjects(osg::State* state) const override
{
_xrState->releaseGLObjects(state);
}
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
+94
View File
@@ -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::BLEND_MODE_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;
}
}
+79
View File
@@ -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;
}
}
+20
View File
@@ -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