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