1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-29 22:45:34 +00:00

First commit. Wrote an osgviewer, achieved display.

This commit is contained in:
Mads Buvik Sandvei 2020-01-10 00:10:09 +01:00
parent 2693598d82
commit 2778775070
19 changed files with 2723 additions and 11 deletions

View file

@ -9,7 +9,8 @@ option(BUILD_BSATOOL "Build BSA extractor" ON)
option(BUILD_ESMTOOL "Build ESM inspector" ON)
option(BUILD_NIFTEST "Build nif file tester" ON)
option(BUILD_MYGUI_PLUGIN "Build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON)
option(BUILD_DOCS "Build documentation." OFF )
option(BUILD_DOCS "Build documentation." OFF)
option(BUILD_VR_OPENXR "Build VR support using OpenXR" on)
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
@ -331,6 +332,12 @@ include_directories("."
${Bullet_INCLUDE_DIRS}
)
if(BUILD_VR_OPENXR)
find_package(OpenXR REQUIRED)
message(STATUS "OpenXR_FOUND: ${OpenXR_FOUND}")
include_directories(${OpenXR_INCLUDE_DIR})
endif(BUILD_VR_OPENXR)
link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS})
if(MYGUI_STATIC)
@ -688,6 +695,23 @@ if (WIN32)
set_target_properties(openmw PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
endif()
# TODO: properties and link targets should be copied from openmw to openmw_vr instead of duplicating every line
if (USE_DEBUG_CONSOLE AND BUILD_VR_OPENXR)
set_target_properties(openmw_vr PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
set_target_properties(openmw_vr PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE")
set_target_properties(openmw_vr PROPERTIES COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_CONSOLE>)
elseif (BUILD_VR_OPENXR)
# Turn off debug console, debug output will be written to visual studio output instead
set_target_properties(openmw_vr PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS")
set_target_properties(openmw_vr PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS")
endif()
if (BUILD_VR_OPENXR)
# Release builds don't use the debug console
set_target_properties(openmw_vr PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
set_target_properties(openmw_vr PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
endif()
# Play a bit with the warning levels
set(WARNINGS "/Wall") # Since windows can only disable specific warnings, not enable them
@ -787,6 +811,14 @@ if (WIN32)
endif()
endif()
if (BUILD_VR_OPENXR)
if (OPENMW_UNITY_BUILD)
set_target_properties(openmw_vr PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj")
else()
set_target_properties(openmw_vr PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif()
endif()
if (BUILD_WIZARD)
set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
endif()

View file

@ -111,13 +111,48 @@ else ()
)
endif ()
if(BUILD_VR_OPENXR)
set(OPENMW_VR_FILES
engine_vr.cpp
mwvr/openxrmanager.hpp
mwvr/openxrmanager.cpp
mwvr/openxrviewer.hpp
mwvr/openxrviewer.cpp
mwvr/openxrmanagerimpl.hpp
mwvr/openxrmanagerimpl.cpp
mwvr/openxrview.hpp
mwvr/openxrview.cpp
mwvr/openxrinputmanager.hpp
mwvr/openxrinputmanager.cpp
mwvr/openxrtexture.hpp
mwvr/openxrtexture.cpp
)
openmw_add_executable(openmw_vr
${OPENMW_FILES}
${OPENMW_VR_FILES}
${GAME} ${GAME_HEADER}
${APPLE_BUNDLE_RESOURCES}
)
# Preprocessor variable used to control code paths in engine.hpp/engine.cpp
# to enable VR output and controls.
# Bizarrely, this doesn't do anything.
# target_compile_definitions(openmw_vr PUBLIC USE_OPENXR XR_USE_GRAPHICS_API_OPENGL XR_USE_PLATFORM_WIN32)
target_compile_options(openmw_vr PUBLIC -DUSE_OPENXR -DXR_USE_GRAPHICS_API_OPENGL -DXR_USE_PLATFORM_WIN32)
endif()
# Sound stuff - here so CMake doesn't stupidly recompile EVERYTHING
# when we change the backend.
include_directories(
${FFmpeg_INCLUDE_DIRS}
)
target_link_libraries(openmw
set(OPENMW_LINK_TARGETS
${OSG_LIBRARIES}
${OPENTHREADS_LIBRARIES}
${OSGPARTICLE_LIBRARIES}
@ -140,6 +175,9 @@ target_link_libraries(openmw
components
)
target_link_libraries(openmw ${OPENMW_LINK_TARGETS})
target_link_libraries(openmw_vr ${OPENMW_LINK_TARGETS} ${OpenXR_LIBRARIES})
if (ANDROID)
set (OSG_PLUGINS
-Wl,--whole-archive
@ -152,7 +190,7 @@ if (ANDROID)
${OSG_PLUGINS} -Wl,--no-whole-archive
)
target_link_libraries(openmw
target_link_libraries(openmw
EGL
android
log
@ -166,16 +204,19 @@ if (ANDROID)
endif (ANDROID)
if (USE_SYSTEM_TINYXML)
target_link_libraries(openmw ${TinyXML_LIBRARIES})
target_link_libraries(openmw ${TinyXML_LIBRARIES})
target_link_libraries(openmw_vr ${TinyXML_LIBRARIES})
endif()
if (NOT UNIX)
target_link_libraries(openmw ${SDL2MAIN_LIBRARY})
target_link_libraries(openmw ${SDL2MAIN_LIBRARY})
target_link_libraries(openmw_vr ${SDL2MAIN_LIBRARY})
endif()
# Fix for not visible pthreads functions for linker with glibc 2.15
if (UNIX AND NOT APPLE)
target_link_libraries(openmw ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(openmw ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(openmw_vr ${CMAKE_THREAD_LIBS_INIT})
endif()
if(APPLE)
@ -196,20 +237,27 @@ if(APPLE)
POST_BUILD
COMMAND cp "${OpenMW_BINARY_DIR}/resources/version" "${BUNDLE_RESOURCES_DIR}/resources")
add_custom_command(TARGET openmw_vr
POST_BUILD
COMMAND cp "${OpenMW_BINARY_DIR}/resources/version" "${BUNDLE_RESOURCES_DIR}/resources")
find_library(COCOA_FRAMEWORK Cocoa)
find_library(IOKIT_FRAMEWORK IOKit)
target_link_libraries(openmw ${COCOA_FRAMEWORK} ${IOKIT_FRAMEWORK})
target_link_libraries(openmw ${COCOA_FRAMEWORK} ${IOKIT_FRAMEWORK})
target_link_libraries(openmw_vr ${COCOA_FRAMEWORK} ${IOKIT_FRAMEWORK})
if (FFmpeg_FOUND)
find_library(COREVIDEO_FRAMEWORK CoreVideo)
find_library(VDA_FRAMEWORK VideoDecodeAcceleration)
target_link_libraries(openmw z ${COREVIDEO_FRAMEWORK} ${VDA_FRAMEWORK})
target_link_libraries(openmw z ${COREVIDEO_FRAMEWORK} ${VDA_FRAMEWORK})
target_link_libraries(openmw_vr z ${COREVIDEO_FRAMEWORK} ${VDA_FRAMEWORK})
endif()
endif(APPLE)
if (BUILD_WITH_CODE_COVERAGE)
add_definitions (--coverage)
target_link_libraries(openmw gcov)
target_link_libraries(openmw gcov)
target_link_libraries(openmw_vr gcov)
endif()
if (MSVC)
@ -220,5 +268,6 @@ if (MSVC)
endif (MSVC)
if (WIN32)
INSTALL(TARGETS openmw RUNTIME DESTINATION ".")
INSTALL(TARGETS openmw RUNTIME DESTINATION ".")
INSTALL(TARGETS openmw_vr RUNTIME DESTINATION ".")
endif (WIN32)

View file

@ -432,6 +432,10 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
camera->setGraphicsContext(graphicsWindow);
camera->setViewport(0, 0, traits->width, traits->height);
#ifdef USE_OPENXR
initVr();
#endif
mViewer->realize();
mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, traits->width, traits->height);
@ -469,6 +473,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
createWindow(settings);
osg::ref_ptr<osg::Group> rootNode (new osg::Group);
mViewer->setSceneData(rootNode);
mVFS.reset(new VFS::Manager(mFSStrict));
@ -687,6 +692,7 @@ void OMW::Engine::go()
// Setup viewer
mViewer = new osgViewer::Viewer;
mViewer->setReleaseContextAtEndOfFrameHint(false);
mViewer->setThreadingModel(osgViewer::Viewer::SingleThreaded);
#if OSG_VERSION_GREATER_OR_EQUAL(3,5,5)
// Do not try to outsmart the OS thread scheduler (see bug #4785).
@ -745,6 +751,13 @@ void OMW::Engine::go()
mEnvironment.getWindowManager()->executeInConsole(mStartupScript);
}
#ifdef USE_OPENXR
auto* root = mViewer->getSceneData();
mXRViewer->addChild(root);
mViewer->setSceneData(mXRViewer);
#endif
// Start the main rendering loop
osg::Timer frameTimer;
double simulationTime = 0.0;

View file

@ -13,6 +13,11 @@
#include "mwworld/ptr.hpp"
#ifdef USE_OPENXR
#include "mwvr/openxrmanager.hpp"
#include "mwvr/openxrviewer.hpp"
#endif
namespace Resource
{
class ResourceSystem;
@ -63,6 +68,7 @@ namespace osgViewer
class ScreenCaptureHandler;
}
struct SDL_Window;
namespace OMW
@ -205,6 +211,13 @@ namespace OMW
private:
Files::ConfigurationManager& mCfgMgr;
#ifdef USE_OPENXR
osg::ref_ptr<MWVR::OpenXRManager> mXR;
osg::ref_ptr<MWVR::OpenXRViewer> mXRViewer;
void initVr();
#endif
};
}

20
apps/openmw/engine_vr.cpp Normal file
View file

@ -0,0 +1,20 @@
#include "engine.hpp"
#include "mwvr/openxrmanager.hpp"
#ifndef USE_OPENXR
#error "USE_OPENXR not defined"
#endif
void OMW::Engine::initVr()
{
if (!mViewer)
throw std::logic_error("mViewer must be initialized before calling initVr()");
mXR = new MWVR::OpenXRManager();
osg::ref_ptr<MWVR::OpenXRManager::RealizeOperation> realizeOperation = new MWVR::OpenXRManager::RealizeOperation(mXR);
mViewer->setRealizeOperation(realizeOperation);
mXRViewer = new MWVR::OpenXRViewer(mXR, realizeOperation, mViewer, 1.f);
// Viewers must be the top node of the scene.
//mViewer->setSceneData(mXRViewer);
}

View file

@ -265,7 +265,7 @@ namespace MWInput
int mFakeDeviceID; //As we only support one controller at a time, use a fake deviceID so we don't lose bindings when switching controllers
private:
public:
enum Actions
{
// please add new actions at the bottom, in order to preserve the channel IDs in the key configuration files

View file

@ -0,0 +1,738 @@
#include "openxrinputmanager.hpp"
#include "openxrmanager.hpp"
#include "openxrmanagerimpl.hpp"
#include "../mwinput/inputmanagerimp.hpp"
#include <components/debug/debuglog.hpp>
#include <components/sdlutil/sdlgraphicswindow.hpp>
#include <openxr/openxr.h>
#include <osg/Camera>
#include <vector>
#include <array>
#include <iostream>
namespace MWVR
{
struct OpenXRAction
{
// I want these to manage XrAction objects but i also didn't want to wrap these in unique pointers (useless dynamic allocation).
// So i implemented move for them instead.
OpenXRAction() = delete;
OpenXRAction(OpenXRAction&& rhs) { mAction = nullptr; *this = std::move(rhs); }
void operator=(OpenXRAction&& rhs);
private:
OpenXRAction(const OpenXRAction&) = default;
OpenXRAction& operator=(const OpenXRAction&) = default;
public:
OpenXRAction(osg::ref_ptr<OpenXRManager> XR, XrAction action, XrActionType actionType, const std::string& actionName, const std::string& localName);
~OpenXRAction();
operator XrAction() { return mAction; }
bool getFloat(XrPath subactionPath, float& value);
bool getBool(XrPath subactionPath, bool& value);
bool getPose(XrPath subactionPath);
bool applyHaptics(XrPath subactionPath, float amplitude);
osg::ref_ptr<OpenXRManager> mXR = nullptr;
XrAction mAction = XR_NULL_HANDLE;
XrActionType mType;
std::string mName;
std::string mLocalName;
};
//! Wrapper around bool type input actions, ignoring all subactions
struct OpenXRAction_Bool
{
OpenXRAction_Bool(OpenXRAction action);
operator XrAction() { return mAction; }
void update();
//! True if action changed to being pressed in the last update
bool actionOnPress() { return actionIsPressed() && actionChanged(); }
//! True if action changed to being released in the last update
bool actionOnRelease() { return actionIsReleased() && actionChanged(); };
//! True if the action is currently being pressed
bool actionIsPressed() { return mPressed; }
//! True if the action is currently not being pressed
bool actionIsReleased() { return !mPressed; }
//! True if the action changed state in the last update
bool actionChanged() { return mChanged; }
OpenXRAction mAction;
bool mPressed = false;
bool mChanged = false;
};
//! Wrapper around float type input actions, ignoring all subactions
struct OpenXRAction_Float
{
OpenXRAction_Float(OpenXRAction action);
operator XrAction() { return mAction; }
void update();
//! Current value of the control, from -1.f to 1.f for sticks or 0.f to 1.f for controls
float value() { return mValue; }
OpenXRAction mAction;
float mValue = 0.f;
};
struct OpenXRInputManagerImpl
{
using Actions = MWInput::InputManager::Actions;
enum SubAction : signed
{
NONE = -1, //!< Used to ignore subaction or when action has no subaction. Not a valid input to createAction().
// hands should be 0 and 1 as they are the typical use case of needing to index between 2 actions or subactions.
LEFT_HAND = 0, //!< Read action from left-hand controller
RIGHT_HAND = 1, //!< Read action from right-hand controller
GAMEPAD = 2, //!< Read action from a gamepad
HEAD = 3, //!< Read action from HMD
USER = 4, //!< Read action from other devices
SUBACTION_MAX = USER, //!< Used to size subaction arrays. Not a valid input.
};
template<size_t Size>
using ActionPaths = std::array<XrPath, Size>;
using SubActionPaths = ActionPaths<SUBACTION_MAX + 1>;
using ControllerActionPaths = ActionPaths<3>; // left hand, right hand, and gamepad
XrPath generateXrPath(const std::string& path);
ControllerActionPaths generateControllerActionPaths(const std::string& controllerAction);
OpenXRInputManagerImpl(osg::ref_ptr<OpenXRManager> XR);
OpenXRAction createAction(XrActionType actionType, const std::string& actionName, const std::string& localName, const std::vector<SubAction>& subActions = {});
XrActionSet createActionSet(void);
void updateHandTracking();
XrPath subactionPath(SubAction subAction);
void updateControls();
void updateHead();
void updateHands();
osg::ref_ptr<OpenXRManager> mXR;
SubActionPaths mSubactionPath;
XrActionSet mActionSet = XR_NULL_HANDLE;
ControllerActionPaths mSelectPath;
ControllerActionPaths mSqueezeValuePath;
ControllerActionPaths mSqueezeClickPath;
ControllerActionPaths mPosePath;
ControllerActionPaths mHapticPath;
ControllerActionPaths mMenuClickPath;
ControllerActionPaths mThumbstickPath;
ControllerActionPaths mThumbstickXPath;
ControllerActionPaths mThumbstickYPath;
ControllerActionPaths mThumbstickClickPath;
ControllerActionPaths mXPath;
ControllerActionPaths mYPath;
ControllerActionPaths mAPath;
ControllerActionPaths mBPath;
ControllerActionPaths mTriggerClickPath;
ControllerActionPaths mTriggerValuePath;
OpenXRAction_Bool mGameMenu;
OpenXRAction_Bool mInventory;
OpenXRAction_Bool mActivate;
OpenXRAction_Bool mUse;
OpenXRAction_Bool mJump;
OpenXRAction_Bool mWeapon;
OpenXRAction_Bool mSpell;
OpenXRAction_Bool mCycleSpellLeft;
OpenXRAction_Bool mCycleSpellRight;
OpenXRAction_Bool mCycleWeaponLeft;
OpenXRAction_Bool mCycleWeaponRight;
OpenXRAction_Bool mSneak;
OpenXRAction_Bool mQuickMenu;
OpenXRAction_Float mLookLeftRight;
OpenXRAction_Float mMoveForwardBackward;
OpenXRAction_Float mMoveLeftRight;
//OpenXRAction mUnused;
//OpenXRAction mScreenshot;
//OpenXRAction mConsole;
//OpenXRAction mMoveLeft;
//OpenXRAction mMoveRight;
//OpenXRAction mMoveForward;
//OpenXRAction mMoveBackward;
//OpenXRAction mAutoMove;
//OpenXRAction mRest;
//OpenXRAction mJournal;
//OpenXRAction mRun;
//OpenXRAction mToggleSneak;
//OpenXRAction mAlwaysRun;
//OpenXRAction mQuickSave;
//OpenXRAction mQuickLoad;
//OpenXRAction mToggleWeapon;
//OpenXRAction mToggleSpell;
//OpenXRAction mTogglePOV;
//OpenXRAction mQuickKey1;
//OpenXRAction mQuickKey2;
//OpenXRAction mQuickKey3;
//OpenXRAction mQuickKey4;
//OpenXRAction mQuickKey5;
//OpenXRAction mQuickKey6;
//OpenXRAction mQuickKey7;
//OpenXRAction mQuickKey8;
//OpenXRAction mQuickKey9;
//OpenXRAction mQuickKey10;
//OpenXRAction mQuickKeysMenu;
//OpenXRAction mToggleHUD;
//OpenXRAction mToggleDebug;
//OpenXRAction mLookUpDown;
//OpenXRAction mZoomIn;
//OpenXRAction mZoomOut;
//! Needed to access all the actions that don't fit on the controllers
OpenXRAction_Bool mActionsMenu;
//! Economize buttons by accessing spell actions and weapon actions on the same keys, but with/without this modifier
OpenXRAction_Bool mSpellModifier;
// Hand tracking
OpenXRAction mHandPoseAction;
OpenXRAction mHapticsAction;
std::array<XrSpace, 2> mHandSpace;
std::array<float, 2> mHandScale;
std::array<XrBool32, 2> mHandActive;
std::array<XrSpaceLocation, 2> mHandSpaceLocation{ { {XR_TYPE_SPACE_LOCATION}, {XR_TYPE_SPACE_LOCATION } } };
// Head tracking
osg::Vec3f mHeadForward;
osg::Vec3f mHeadUpward;
osg::Vec3f mHeadRightward;
osg::Vec3f mHeadStagePosition;
osg::Quat mHeadStageOrientation;
osg::Vec3f mHeadWorldPosition;
osg::Quat mHeadWorldOrientation;
};
XrActionSet
OpenXRInputManagerImpl::createActionSet()
{
XrActionSet actionSet = XR_NULL_HANDLE;
XrActionSetCreateInfo createInfo{ XR_TYPE_ACTION_SET_CREATE_INFO };
strcpy_s(createInfo.actionSetName, "gameplay");
strcpy_s(createInfo.localizedActionSetName, "Gameplay");
createInfo.priority = 0;
CHECK_XRCMD(xrCreateActionSet(mXR->mPrivate->mInstance, &createInfo, &actionSet));
return actionSet;
}
void
OpenXRAction::operator=(
OpenXRAction&& rhs)
{
if (mAction)
xrDestroyAction(mAction);
*this = static_cast<OpenXRAction&>(rhs);
rhs.mAction = XR_NULL_HANDLE;
}
OpenXRAction::OpenXRAction(
osg::ref_ptr<OpenXRManager> XR,
XrAction action,
XrActionType actionType,
const std::string& actionName,
const std::string& localName)
: mXR(XR)
, mAction(action)
, mType(actionType)
, mName(actionName)
, mLocalName(localName)
{
};
OpenXRAction::~OpenXRAction() {
if (mAction)
{
xrDestroyAction(mAction);
std::cout << "I destroyed your \"" << mLocalName << "\" action" << std::endl;
}
}
bool OpenXRAction::getFloat(XrPath subactionPath, float& value)
{
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
getInfo.action = mAction;
getInfo.subactionPath = subactionPath;
XrActionStateFloat xrValue{ XR_TYPE_ACTION_STATE_FLOAT };
CHECK_XRCMD(xrGetActionStateFloat(mXR->mPrivate->mSession, &getInfo, &xrValue));
if (xrValue.isActive)
value = xrValue.currentState;
return xrValue.isActive;
}
bool OpenXRAction::getBool(XrPath subactionPath, bool& value)
{
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
getInfo.action = mAction;
getInfo.subactionPath = subactionPath;
XrActionStateBoolean xrValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
CHECK_XRCMD(xrGetActionStateBoolean(mXR->mPrivate->mSession, &getInfo, &xrValue));
if (xrValue.isActive)
value = xrValue.currentState;
return xrValue.isActive;
}
// Pose action only checks if the pose is active or not
bool OpenXRAction::getPose(XrPath subactionPath)
{
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
getInfo.action = mAction;
getInfo.subactionPath = subactionPath;
XrActionStatePose xrValue{ XR_TYPE_ACTION_STATE_POSE };
CHECK_XRCMD(xrGetActionStatePose(mXR->mPrivate->mSession, &getInfo, &xrValue));
return xrValue.isActive;
}
bool OpenXRAction::applyHaptics(XrPath subactionPath, float amplitude)
{
XrHapticVibration vibration{ XR_TYPE_HAPTIC_VIBRATION };
vibration.amplitude = amplitude;
vibration.duration = XR_MIN_HAPTIC_DURATION;
vibration.frequency = XR_FREQUENCY_UNSPECIFIED;
XrHapticActionInfo hapticActionInfo{ XR_TYPE_HAPTIC_ACTION_INFO };
hapticActionInfo.action = mAction;
hapticActionInfo.subactionPath = subactionPath;
CHECK_XRCMD(xrApplyHapticFeedback(mXR->mPrivate->mSession, &hapticActionInfo, (XrHapticBaseHeader*)&vibration));
return true;
}
OpenXRAction_Bool::OpenXRAction_Bool(
OpenXRAction action)
: mAction(std::move(action))
{
}
void
OpenXRAction_Bool::update()
{
bool old = mPressed;
mAction.getBool(0, mPressed);
mChanged = mPressed != old;
}
OpenXRAction_Float::OpenXRAction_Float(
OpenXRAction action)
: mAction(std::move(action))
{
}
void
OpenXRAction_Float::update()
{
mAction.getFloat(0, mValue);
}
OpenXRInputManagerImpl::ControllerActionPaths
OpenXRInputManagerImpl::generateControllerActionPaths(
const std::string& controllerAction)
{
ControllerActionPaths actionPaths;
std::string left = std::string("/user/hand/left") + controllerAction;
std::string right = std::string("/user/hand/right") + controllerAction;
std::string pad = std::string("/user/gamepad") + controllerAction;
CHECK_XRCMD(xrStringToPath(mXR->mPrivate->mInstance, left.c_str(), &actionPaths[LEFT_HAND]));
CHECK_XRCMD(xrStringToPath(mXR->mPrivate->mInstance, right.c_str(), &actionPaths[RIGHT_HAND]));
CHECK_XRCMD(xrStringToPath(mXR->mPrivate->mInstance, pad.c_str(), &actionPaths[GAMEPAD]));
return actionPaths;
}
OpenXRInputManagerImpl::OpenXRInputManagerImpl(
osg::ref_ptr<OpenXRManager> XR)
: mXR(XR)
, mSubactionPath{ {
generateXrPath("/user/hand/left"),
generateXrPath("/user/hand/right"),
generateXrPath("/user/gamepad"),
generateXrPath("/user/head"),
generateXrPath("/user")
} }
, mActionSet(createActionSet())
, mSelectPath(generateControllerActionPaths("/input/select/click"))
, mSqueezeValuePath(generateControllerActionPaths("/input/squeeze/value"))
, mSqueezeClickPath(generateControllerActionPaths("/input/squeeze/click"))
, mPosePath(generateControllerActionPaths("/input/grip/pose"))
, mHapticPath(generateControllerActionPaths("/output/haptic"))
, mMenuClickPath(generateControllerActionPaths("/input/menu/click"))
, mThumbstickPath(generateControllerActionPaths("/input/thumbstick/value"))
, mThumbstickXPath(generateControllerActionPaths("/input/thumbstick/x"))
, mThumbstickYPath(generateControllerActionPaths("/input/thumbstick/y"))
, mThumbstickClickPath(generateControllerActionPaths("/input/thumbstick/click"))
, mXPath(generateControllerActionPaths("/input/x/click"))
, mYPath(generateControllerActionPaths("/input/y/click"))
, mAPath(generateControllerActionPaths("/input/a/click"))
, mBPath(generateControllerActionPaths("/input/b/click"))
, mTriggerValuePath(generateControllerActionPaths("/input/trigger/value"))
, mTriggerClickPath(generateControllerActionPaths("/input/trigger/click"))
, mActionsMenu(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "actions_menu", "Actions Menu", { })))
, mSpellModifier(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "spell_modifier", "Spell Modifier", { })))
, mGameMenu(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "game_menu", "GameMenu", { })))
, mInventory(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "inventory", "Inventory", { })))
, mActivate(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "activate", "Activate", { })))
, mUse(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "use", "Use", { })))
, mJump(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "jump", "Jump", { })))
, mWeapon(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "weapon", "Weapon", { })))
, mSpell(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "spell", "Spell", { })))
, mCycleSpellLeft(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "cycle_spell_left", "CycleSpellLeft", { })))
, mCycleSpellRight(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "cycle_spell_right", "CycleSpellRight", { })))
, mCycleWeaponLeft(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "cycle_weapon_left", "CycleWeaponLeft", { })))
, mCycleWeaponRight(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "cycle_weapon_right", "CycleWeaponRight", { })))
, mSneak(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "sneak", "Sneak", { })))
, mQuickMenu(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "quick_menu", "QuickMenu", { })))
, mLookLeftRight(std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "look_left_right", "LookLeftRight", { })))
, mMoveForwardBackward(std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "move_forward_backward", "MoveForwardBackward", { })))
, mMoveLeftRight(std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "move_left_right", "MoveLeftRight", { })))
, mHandPoseAction(std::move(createAction(XR_ACTION_TYPE_POSE_INPUT, "hand_pose", "Hand Pose", { LEFT_HAND, RIGHT_HAND })))
, mHapticsAction(std::move(createAction(XR_ACTION_TYPE_VIBRATION_OUTPUT, "vibrate_hand", "Vibrate Hand", { LEFT_HAND, RIGHT_HAND })))
{
//{ mHandPoseAction, mPosePath[LEFT_HAND]},
//{ mHandPoseAction, mPosePath[RIGHT_HAND] },
//{ mHapticsAction, mHapticPath[LEFT_HAND] },
//{ mHapticsAction, mHapticPath[RIGHT_HAND] },
//{ mLookLeftRight, mThumbstickXPath[RIGHT_HAND] },
//{ mMoveLeftRight, mThumbstickXPath[LEFT_HAND] },
//{ mMoveForwardBackward, mThumbstickYPath[LEFT_HAND] },
//{ mActivate, mSqueezeClickPath[RIGHT_HAND] },
//{ mUse, mTriggerClickPath[RIGHT_HAND] },
//{ mJump, mTriggerValuePath[LEFT_HAND] },
//{ mWeapon, mAPath[RIGHT_HAND] },
//{ mSpell, mAPath[RIGHT_HAND] },
//{ mCycleSpellLeft, mThumbstickClickPath[LEFT_HAND] },
//{ mCycleSpellRight, mThumbstickClickPath[RIGHT_HAND] },
//{ mCycleWeaponLeft, mThumbstickClickPath[LEFT_HAND] },
//{ mCycleWeaponRight, mThumbstickClickPath[RIGHT_HAND] },
//{ mSneak, mXPath[LEFT_HAND] },
//{ mInventory, mBPath[RIGHT_HAND] },
//{ mQuickMenu, mYPath[LEFT_HAND] },
//{ mSpellModifier, mSqueezeClickPath[LEFT_HAND] },
//{ mGameMenu, mMenuClickPath[LEFT_HAND] },
// There are not enough actions on controllers to assign everything.
//mUnused = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "Unused", "Unused", { }));
//mScreenshot = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "Screenshot", "Screenshot", { }));
//mConsole = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "Console", "Console", { }));
//mMoveLeft = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "MoveLeft", "MoveLeft", { }));
//mMoveRight = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "MoveRight", "MoveRight", { }));
//mMoveForward = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "MoveForward", "MoveForward", { }));
//mMoveBackward = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "MoveBackward", "MoveBackward", { }));
//mAutoMove = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "AutoMove", "AutoMove", { }));
//mRest = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "Rest", "Rest", { }));
//mJournal = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "Journal", "Journal", { }));
//mRun = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "Run", "Run", { }));
//mAlwaysRun = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "AlwaysRun", "AlwaysRun", { }));
//mQuickSave = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "QuickSave", "QuickSave", { }));
//mQuickLoad = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "QuickLoad", "QuickLoad", { }));
//mToggleWeapon = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "ToggleWeapon", "ToggleWeapon", { }));
//mToggleSpell = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "ToggleSpell", "ToggleSpell", { }));
//mTogglePOV = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "TogglePOV", "TogglePOV", { }));
//mQuickKey1 = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "QuickKey1", "QuickKey1", { }));
//mQuickKey2 = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "QuickKey2", "QuickKey2", { }));
//mQuickKey3 = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "QuickKey3", "QuickKey3", { }));
//mQuickKey4 = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "QuickKey4", "QuickKey4", { }));
//mQuickKey5 = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "QuickKey5", "QuickKey5", { }));
//mQuickKey6 = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "QuickKey6", "QuickKey6", { }));
//mQuickKey7 = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "QuickKey7", "QuickKey7", { }));
//mQuickKey8 = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "QuickKey8", "QuickKey8", { }));
//mQuickKey9 = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "QuickKey9", "QuickKey9", { }));
//mQuickKey10 = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "QuickKey10", "QuickKey10", { }));
//mQuickKeysMenu = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "QuickKeysMenu", "QuickKeysMenu", { }));
//mToggleHUD = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "ToggleHUD", "ToggleHUD", { }));
//mToggleDebug = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "ToggleDebug", "ToggleDebug", { }));
//mToggleSneak = std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "ToggleSneak", "ToggleSneak", { }));
//mLookUpDown = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "LookUpDown", "LookUpDown", { }));
//mZoomIn = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "ZoomIn", "ZoomIn", { }));
//mZoomOut = std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "ZoomOut", "ZoomOut", { }));
{ // Set up default bindings for the oculus
XrPath oculusTouchInteractionProfilePath;
CHECK_XRCMD(
xrStringToPath(XR->mPrivate->mInstance, "/interaction_profiles/oculus/touch_controller", &oculusTouchInteractionProfilePath));
std::vector<XrActionSuggestedBinding> bindings{ {
{mHandPoseAction, mPosePath[LEFT_HAND]},
{mHandPoseAction, mPosePath[RIGHT_HAND]},
{mHapticsAction, mHapticPath[LEFT_HAND]},
{mHapticsAction, mHapticPath[RIGHT_HAND]},
{mLookLeftRight, mThumbstickXPath[RIGHT_HAND]},
{mMoveLeftRight, mThumbstickXPath[LEFT_HAND]},
{mMoveForwardBackward, mThumbstickYPath[LEFT_HAND]},
{mActivate, mSqueezeClickPath[RIGHT_HAND]},
{mUse, mTriggerClickPath[RIGHT_HAND]},
{mJump, mTriggerValuePath[LEFT_HAND]},
{mWeapon, mAPath[RIGHT_HAND]},
{mSpell, mAPath[RIGHT_HAND]},
{mCycleSpellLeft, mThumbstickClickPath[LEFT_HAND]},
{mCycleSpellRight, mThumbstickClickPath[RIGHT_HAND]},
{mCycleWeaponLeft, mThumbstickClickPath[LEFT_HAND]},
{mCycleWeaponRight, mThumbstickClickPath[RIGHT_HAND]},
{mSneak, mXPath[LEFT_HAND]},
{mInventory, mBPath[RIGHT_HAND]},
{mQuickMenu, mYPath[LEFT_HAND]},
{mSpellModifier, mSqueezeClickPath[LEFT_HAND]},
{mGameMenu, mMenuClickPath[LEFT_HAND]},
} };
XrInteractionProfileSuggestedBinding suggestedBindings{ XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING };
suggestedBindings.interactionProfile = oculusTouchInteractionProfilePath;
suggestedBindings.suggestedBindings = bindings.data();
suggestedBindings.countSuggestedBindings = (uint32_t)bindings.size();
CHECK_XRCMD(xrSuggestInteractionProfileBindings(XR->mPrivate->mInstance, &suggestedBindings));
/*
mSpellModifier; // L-Squeeze
mActivate; // R-Squeeze
mUse; // R-Trigger
mJump; // L-Trigger. L-trigger has value, can be used to make measured jumps
mWeapon; // A
mSpell; // A + SpellModifier
mRun; // Based on movement thumbstick value: broken line ( 0 (stand), 0.5 (walk), 1.0 (run) ). Remember to scale fatigue use.
mCycleSpellLeft; // L-ThumbstickClick + SpellModifier
mCycleSpellRight; // R-ThumbstickClick + SpellModifier
mCycleWeaponLeft; // L-ThumbstickClick
mCycleWeaponRight; // R-ThumbstickClick
mSneak; // X
mInventory; // B
mQuickMenu; // Y
mGameMenu; // Menu
*/
}
{ // Set up action spaces
XrActionSpaceCreateInfo createInfo{ XR_TYPE_ACTION_SPACE_CREATE_INFO };
createInfo.action = mHandPoseAction;
createInfo.poseInActionSpace.orientation.w = 1.f;
createInfo.subactionPath = mSubactionPath[LEFT_HAND];
CHECK_XRCMD(xrCreateActionSpace(XR->mPrivate->mSession, &createInfo, &mHandSpace[LEFT_HAND]));
createInfo.subactionPath = mSubactionPath[RIGHT_HAND];
CHECK_XRCMD(xrCreateActionSpace(XR->mPrivate->mSession, &createInfo, &mHandSpace[RIGHT_HAND]));
}
{ // Set up the action set
XrSessionActionSetsAttachInfo attachInfo{ XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO };
attachInfo.countActionSets = 1;
attachInfo.actionSets = &mActionSet;
CHECK_XRCMD(xrAttachSessionActionSets(XR->mPrivate->mSession, &attachInfo));
}
};
OpenXRAction
OpenXRInputManagerImpl::createAction(
XrActionType actionType,
const std::string& actionName,
const std::string& localName,
const std::vector<SubAction>& subActions)
{
std::vector<XrPath> subactionPaths;
XrActionCreateInfo createInfo{ XR_TYPE_ACTION_CREATE_INFO };
createInfo.actionType = actionType;
strcpy_s(createInfo.actionName, actionName.c_str());
strcpy_s(createInfo.localizedActionName, localName.c_str());
if (!subActions.empty())
{
for (auto subAction : subActions)
subactionPaths.push_back(subactionPath(subAction));
createInfo.countSubactionPaths = subactionPaths.size();
createInfo.subactionPaths = subactionPaths.data();
}
XrAction action = XR_NULL_HANDLE;
CHECK_XRCMD(xrCreateAction(mActionSet, &createInfo, &action));
return OpenXRAction(mXR, action, actionType, actionName, localName);
}
void
OpenXRInputManagerImpl::updateHandTracking()
{
for (auto hand : { LEFT_HAND, RIGHT_HAND }) {
CHECK_XRCMD(xrLocateSpace(mHandSpace[hand], mXR->mPrivate->mReferenceSpaceStage, mXR->mPrivate->mFrameState.predictedDisplayTime, &mHandSpaceLocation[hand]));
}
}
XrPath
OpenXRInputManagerImpl::subactionPath(
SubAction subAction)
{
if (subAction == NONE)
return 0;
return mSubactionPath[subAction];
}
void
OpenXRInputManagerImpl::updateControls()
{
if (!mXR->mPrivate->mSessionRunning)
return;
// TODO: Should be in OpenXRViewer
//mXR->waitFrame();
//mXR->beginFrame();
updateHead();
updateHands();
// This set of actions only care about on-press
if (mActionsMenu.actionOnPress())
{
// Generate on press action
}
if (mGameMenu.actionOnPress())
{
// Generate on press action
}
if (mInventory.actionOnPress())
{
// Generate on press action
}
if (mActivate.actionOnPress())
{
// Generate on press action
}
if (mUse.actionOnPress())
{
// Generate on press action
}
if (mJump.actionOnPress())
{
// Generate on press action
}
if (mSneak.actionOnPress())
{
// Generate on press action
}
if (mQuickMenu.actionOnPress())
{
// Generate on press action
}
// Weapon/Spell actions
if (mWeapon.actionOnPress() && !mSpellModifier.actionIsPressed())
{
// Generate on press action
}
if (mCycleWeaponLeft.actionOnPress() && !mSpellModifier.actionIsPressed())
{
// Generate on press action
}
if (mCycleWeaponRight.actionOnPress() && !mSpellModifier.actionIsPressed())
{
// Generate on press action
}
if (mSpell.actionOnPress() && mSpellModifier.actionIsPressed())
{
// Generate on press action
}
if (mCycleSpellLeft.actionOnPress() && mSpellModifier.actionIsPressed())
{
// Generate on press action
}
if (mCycleSpellRight.actionOnPress() && mSpellModifier.actionIsPressed())
{
// Generate on press action
}
float lookLeftRight = mLookLeftRight.value();
float moveLeftRight = mMoveLeftRight.value();
float moveForwardBackward = mMoveForwardBackward.value();
// Propagate movement to openmw
}
void OpenXRInputManagerImpl::updateHead()
{
//auto location = mXR->getHeadLocation();
////std::stringstream ss;
////ss << "Head.pose=< position=<"
//// << location.pose.position.x << ", " << location.pose.position.y << ", " << location.pose.position.z << "> orientation=<"
//// << location.pose.orientation.x << ", " << location.pose.orientation.y << ", " << location.pose.orientation.z << ", " << location.pose.orientation.w << "> >";
////mOpenXRLogger.log(ss.str(), 5, "Tracking", "OpenXR");
//// To keep world movement from physical walking properly oriented, world position must be tracked in differentials from stage position, as orientation between
//// stage and world may differ.
//osg::Vec3f newHeadStagePosition{ location.pose.position.x, location.pose.position.y, location.pose.position.z };
//osg::Vec3f headStageMovement = newHeadStagePosition - mHeadStagePosition;
//osg::Vec3f headWorldMovement = yaw() * headStageMovement;
//// Update positions
//mHeadStagePosition = newHeadStagePosition;
//mHeadWorldPosition = mHeadWorldPosition + headWorldMovement;
//// Update orientations
//mHeadStageOrientation = osg::fromXR(location.pose.orientation);
//mHeadWorldOrientation = yaw() * mHeadStageOrientation;
//osg::Vec3f up(0.f, 1.f, 0.f);
//up = mHeadStageOrientation * up;
//mHeadUpward = (mHeadWorldOrientation * osg::Vec3f(0.f, 1.f, 0.f));
//mHeadUpward.normalize();
//mHeadForward = (mHeadWorldOrientation * osg::Vec3f(0.f, 0.f, -1.f));
//mHeadForwardmHeadForward.normalize();
//mHeadRightward = mHeadForward ^ mHeadUpward;
//mHeadRightward.normalize();
}
void OpenXRInputManagerImpl::updateHands()
{
// TODO
}
XrPath OpenXRInputManagerImpl::generateXrPath(const std::string& path)
{
XrPath xrpath = 0;
CHECK_XRCMD(xrStringToPath(mXR->mPrivate->mInstance, path.c_str(), &xrpath));
return xrpath;
}
OpenXRInputManager::OpenXRInputManager(
osg::ref_ptr<OpenXRManager> XR)
: mPrivate(new OpenXRInputManagerImpl(XR))
{
}
OpenXRInputManager::~OpenXRInputManager()
{
}
void
OpenXRInputManager::updateControls()
{
mPrivate->updateControls();
}
}

View file

@ -0,0 +1,25 @@
#ifndef OPENXR_INPUT_MANAGER_HPP
#define OPENXR_INPUT_MANAGER_HPP
#include "openxrmanager.hpp"
#include "../mwinput/inputmanagerimp.hpp"
#include <vector>
#include <array>
#include <iostream>
namespace MWVR
{
struct OpenXRInputManagerImpl;
struct OpenXRInputManager
{
OpenXRInputManager(osg::ref_ptr<OpenXRManager> XR);
~OpenXRInputManager();
void updateControls();
std::unique_ptr<OpenXRInputManagerImpl> mPrivate;
};
}
#endif

View file

@ -0,0 +1,160 @@
#include "openxrmanager.hpp"
#include "openxrmanagerimpl.hpp"
#include "../mwinput/inputmanagerimp.hpp"
#include <components/debug/debuglog.hpp>
#include <components/sdlutil/sdlgraphicswindow.hpp>
#include <Windows.h>
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#include <openxr/openxr_platform_defines.h>
#include <openxr/openxr_reflection.h>
#include <osg/Camera>
#include <vector>
#include <array>
#include <iostream>
namespace MWVR
{
OpenXRManager::OpenXRManager()
: mPrivate(nullptr)
, mMutex()
{
}
OpenXRManager::~OpenXRManager()
{
}
bool
OpenXRManager::realized()
{
return !!mPrivate;
}
long long
OpenXRManager::frameIndex()
{
if (realized())
return mPrivate->frameIndex;
}
bool OpenXRManager::sessionRunning()
{
if (realized())
return mPrivate->mSessionRunning;
return false;
}
void OpenXRManager::handleEvents()
{
if (realized())
return mPrivate->handleEvents();
}
void OpenXRManager::waitFrame()
{
if (realized())
return mPrivate->waitFrame();
}
void OpenXRManager::beginFrame()
{
if (realized())
return mPrivate->beginFrame();
}
void OpenXRManager::endFrame()
{
if (realized())
return mPrivate->endFrame();
}
void OpenXRManager::updateControls()
{
if (realized())
return mPrivate->updateControls();
}
void OpenXRManager::updatePoses()
{
if (realized())
return mPrivate->updatePoses();
}
void
OpenXRManager::realize(
osg::GraphicsContext* gc)
{
lock_guard lock(mMutex);
if (!realized())
{
gc->makeCurrent();
try {
mPrivate = std::make_shared<OpenXRManagerImpl>();
}
catch (std::exception& e)
{
Log(Debug::Error) << "Exception thrown by OpenXR: " << e.what();
osg::ref_ptr<osg::State> state = gc->getState();
}
}
}
void OpenXRManager::setPoseUpdateCallback(PoseUpdateCallback::TrackedLimb limb, PoseUpdateCallback::TrackingMode mode, osg::ref_ptr<PoseUpdateCallback> cb)
{
if (realized())
return mPrivate->setPoseUpdateCallback(limb, mode, cb);
}
void OpenXRManager::setViewSubImage(int eye, const ::XrSwapchainSubImage& subImage)
{
if (realized())
return mPrivate->setViewSubImage(eye, subImage);
}
int OpenXRManager::eyes()
{
if (realized())
return mPrivate->eyes();
return 0;
}
void
OpenXRManager::RealizeOperation::operator()(
osg::GraphicsContext* gc)
{
osg::ref_ptr<OpenXRManager> XR;
mXR->realize(gc);
}
bool
OpenXRManager::RealizeOperation::realized()
{
return mXR->realized();
}
void
OpenXRManager::CleanupOperation::operator()(
osg::GraphicsContext* gc)
{
}
void
OpenXRManager::SwapBuffersCallback::swapBuffersImplementation(
osg::GraphicsContext* gc)
{
gc->swapBuffersImplementation();
mXR->endFrame();
}
}

View file

@ -0,0 +1,116 @@
#ifndef MWVR_OPENRXMANAGER_H
#define MWVR_OPENRXMANAGER_H
#ifndef USE_OPENXR
#error "openxrmanager.hpp included without USE_OPENXR defined"
#endif
#include <memory>
#include <mutex>
#include <components/sdlutil/sdlgraphicswindow.hpp>
#include <components/settings/settings.hpp>
#include <osg/Camera>
#include <osgViewer/Viewer>
struct XrSwapchainSubImage;
namespace MWVR
{
// Use the pimpl pattern to avoid cluttering the namespace with openxr dependencies.
class OpenXRManagerImpl;
class OpenXRManager : public osg::Referenced
{
public:
class PoseUpdateCallback: public osg::Referenced
{
public:
enum TrackedLimb
{
LEFT_HAND,
RIGHT_HAND,
HEAD
};
//! Describes how position is tracked. Orientation is always absolute.
enum TrackingMode
{
STAGE_ABSOLUTE, //!< Position in VR stage. Meaning relative to some floor level origin
STAGE_RELATIVE, //!< Same as STAGE_ABSOLUTE but receive only changes in position
};
virtual void operator()(osg::Vec3 position, osg::Quat orientation) = 0;
};
public:
class RealizeOperation : public osg::GraphicsOperation
{
public:
RealizeOperation(osg::ref_ptr<OpenXRManager> XR) : osg::GraphicsOperation("OpenXRRealizeOperation", false), mXR(XR) {};
void operator()(osg::GraphicsContext* gc) override;
bool realized();
private:
osg::ref_ptr<OpenXRManager> mXR;
};
class CleanupOperation : public osg::GraphicsOperation
{
public:
CleanupOperation(osg::ref_ptr<OpenXRManager> XR) : osg::GraphicsOperation("OpenXRCleanupOperation", false), mXR(XR) {};
void operator()(osg::GraphicsContext* gc) override;
private:
osg::ref_ptr<OpenXRManager> mXR;
};
class SwapBuffersCallback : public osg::GraphicsContext::SwapCallback
{
public:
SwapBuffersCallback(osg::ref_ptr<OpenXRManager> XR) : mXR(XR) {};
void swapBuffersImplementation(osg::GraphicsContext* gc) override;
private:
osg::ref_ptr<OpenXRManager> mXR;
};
public:
OpenXRManager();
~OpenXRManager();
bool realized();
long long frameIndex();
bool sessionRunning();
void handleEvents();
void waitFrame();
void beginFrame();
void endFrame();
void updateControls();
void updatePoses();
void realize(osg::GraphicsContext* gc);
void setPoseUpdateCallback(PoseUpdateCallback::TrackedLimb limb, PoseUpdateCallback::TrackingMode mode, osg::ref_ptr<PoseUpdateCallback> cb);
void setViewSubImage(int eye, const ::XrSwapchainSubImage& subImage);
int eyes();
private:
friend class OpenXRViewImpl;
friend class OpenXRInputManagerImpl;
friend class OpenXRAction;
friend class OpenXRViewer;
std::shared_ptr<OpenXRManagerImpl> mPrivate;
std::mutex mMutex;
using lock_guard = std::lock_guard<std::mutex>;
};
}
#endif

View file

@ -0,0 +1,535 @@
#include "openxrmanagerimpl.hpp"
#include "openxrtexture.hpp"
#include <components/debug/debuglog.hpp>
#include <components/sdlutil/sdlgraphicswindow.hpp>
// The OpenXR SDK assumes we've included Windows.h
#include <Windows.h>
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#include <openxr/openxr_platform_defines.h>
#include <openxr/openxr_reflection.h>
#include <osg/Camera>
#include <vector>
#include <array>
#include <iostream>
#define ENUM_CASE_STR(name, val) case name: return #name;
#define MAKE_TO_STRING_FUNC(enumType) \
inline const char* to_string(enumType e) { \
switch (e) { \
XR_LIST_ENUM_##enumType(ENUM_CASE_STR) \
default: return "Unknown " #enumType; \
} \
}
MAKE_TO_STRING_FUNC(XrReferenceSpaceType);
MAKE_TO_STRING_FUNC(XrViewConfigurationType);
MAKE_TO_STRING_FUNC(XrEnvironmentBlendMode);
MAKE_TO_STRING_FUNC(XrSessionState);
MAKE_TO_STRING_FUNC(XrResult);
MAKE_TO_STRING_FUNC(XrFormFactor);
MAKE_TO_STRING_FUNC(XrStructureType);
namespace MWVR
{
OpenXRManagerImpl::OpenXRManagerImpl()
{
std::vector<const char*> extensions = {
XR_KHR_OPENGL_ENABLE_EXTENSION_NAME
};
{ // Create Instance
XrInstanceCreateInfo createInfo{ XR_TYPE_INSTANCE_CREATE_INFO };
createInfo.next = nullptr;
createInfo.enabledExtensionCount = extensions.size();
createInfo.enabledExtensionNames = extensions.data();
strcpy(createInfo.applicationInfo.applicationName, "Boo");
createInfo.applicationInfo.apiVersion = XR_CURRENT_API_VERSION;
CHECK_XRCMD(xrCreateInstance(&createInfo, &mInstance));
assert(mInstance);
}
{ // Get system ID
XrSystemGetInfo systemInfo{ XR_TYPE_SYSTEM_GET_INFO };
systemInfo.formFactor = mFormFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
CHECK_XRCMD(xrGetSystem(mInstance, &systemInfo, &mSystemId));
assert(mSystemId);
}
{ // Initialize OpenGL device
// This doesn't appear to be intended to do anything of consequence, only return requirements to me. But xrCreateSession fails if xrGetOpenGLGraphicsRequirementsKHR is not called.
// Oculus Bug?
PFN_xrGetOpenGLGraphicsRequirementsKHR p_getRequirements = nullptr;
xrGetInstanceProcAddr(mInstance, "xrGetOpenGLGraphicsRequirementsKHR", reinterpret_cast<PFN_xrVoidFunction*>(&p_getRequirements));
XrGraphicsRequirementsOpenGLKHR requirements{ XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR };
CHECK_XRCMD(p_getRequirements(mInstance, mSystemId, &requirements));
//GLint major = 0;
//GLint minor = 0;
//glGetIntegerv(GL_MAJOR_VERSION, &major);
//glGetIntegerv(GL_MINOR_VERSION, &minor);
const XrVersion desiredApiVersion = XR_MAKE_VERSION(4, 6, 0);
if (requirements.minApiVersionSupported > desiredApiVersion) {
std::cout << "Runtime does not support desired Graphics API and/or version" << std::endl;
}
}
{ // Create Session
// TODO: Platform dependent
mGraphicsBinding.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR;
mGraphicsBinding.next = nullptr;
mGraphicsBinding.hDC = wglGetCurrentDC();
mGraphicsBinding.hGLRC = wglGetCurrentContext();
if (!mGraphicsBinding.hDC)
std::cout << "Missing DC" << std::endl;
if (!mGraphicsBinding.hGLRC)
std::cout << "Missing GLRC" << std::endl;
XrSessionCreateInfo createInfo{ XR_TYPE_SESSION_CREATE_INFO };
createInfo.next = &mGraphicsBinding;
createInfo.systemId = mSystemId;
createInfo.createFlags;
CHECK_XRCMD(xrCreateSession(mInstance, &createInfo, &mSession));
assert(mSession);
}
LogLayersAndExtensions();
LogInstanceInfo();
LogReferenceSpaces();
{ // Set up reference space
XrReferenceSpaceCreateInfo createInfo{ XR_TYPE_REFERENCE_SPACE_CREATE_INFO };
createInfo.poseInReferenceSpace.orientation.w = 1.f; // Identity pose
createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
CHECK_XRCMD(xrCreateReferenceSpace(mSession, &createInfo, &mReferenceSpaceView));
createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
CHECK_XRCMD(xrCreateReferenceSpace(mSession, &createInfo, &mReferenceSpaceStage));
}
{ // Set up layers
mLayer.space = mReferenceSpaceStage;
mLayer.viewCount = (uint32_t)mProjectionLayerViews.size();
mLayer.views = mProjectionLayerViews.data();
}
{ // Read and log graphics properties for the swapchain
xrGetSystemProperties(mInstance, mSystemId, &mSystemProperties);
// Log system properties.
{
std::stringstream ss;
ss << "System Properties: Name=" << mSystemProperties.systemName << " VendorId=" << mSystemProperties.vendorId << std::endl;
ss << "System Graphics Properties: MaxWidth=" << mSystemProperties.graphicsProperties.maxSwapchainImageWidth;
ss << " MaxHeight=" << mSystemProperties.graphicsProperties.maxSwapchainImageHeight;
ss << " MaxLayers=" << mSystemProperties.graphicsProperties.maxLayerCount << std::endl;
ss << "System Tracking Properties: OrientationTracking=" << mSystemProperties.trackingProperties.orientationTracking ? "True" : "False";
ss << " PositionTracking=" << mSystemProperties.trackingProperties.positionTracking ? "True" : "False";
Log(Debug::Verbose) << ss.str();
}
uint32_t viewCount = 0;
CHECK_XRCMD(xrEnumerateViewConfigurationViews(mInstance, mSystemId, mViewConfigType, 2, &viewCount, mConfigViews.data()));
if (viewCount != 2)
{
std::stringstream ss;
ss << "xrEnumerateViewConfigurationViews returned " << viewCount << " views";
Log(Debug::Verbose) << ss.str();
}
// TODO: This, including the projection layer views, should be moved to openxrviewer
//for (unsigned i = 0; i < 2; i++)
//{
// mEyes[i].reset(new OpenXRView(this, mConfigViews[i]));
// mProjectionLayerViews[i].subImage.swapchain = mEyes[i]->mSwapchain;
// mProjectionLayerViews[i].subImage.imageRect.offset = { 0, 0 };
// mProjectionLayerViews[i].subImage.imageRect.extent = { mEyes[i]->mWidth, mEyes[i]->mHeight };
//}
}
}
inline XrResult CheckXrResult(XrResult res, const char* originator, const char* sourceLocation) {
if (XR_FAILED(res)) {
Log(Debug::Error) << sourceLocation << ": OpenXR[" << to_string(res) << "]: " << originator;
}
else
{
Log(Debug::Verbose) << sourceLocation << ": OpenXR[" << to_string(res) << "][" << std::this_thread::get_id() << "][" << wglGetCurrentDC() << "][" << wglGetCurrentContext() << "]: " << originator;
}
return res;
}
OpenXRManagerImpl::~OpenXRManagerImpl()
{
}
void
OpenXRManagerImpl::LogLayersAndExtensions() {
// Write out extension properties for a given layer.
const auto logExtensions = [](const char* layerName, int indent = 0) {
uint32_t instanceExtensionCount;
CHECK_XRCMD(xrEnumerateInstanceExtensionProperties(layerName, 0, &instanceExtensionCount, nullptr));
std::vector<XrExtensionProperties> extensions(instanceExtensionCount);
for (XrExtensionProperties& extension : extensions) {
extension.type = XR_TYPE_EXTENSION_PROPERTIES;
}
CHECK_XRCMD(xrEnumerateInstanceExtensionProperties(layerName, (uint32_t)extensions.size(), &instanceExtensionCount,
extensions.data()));
const std::string indentStr(indent, ' ');
std::stringstream ss;
ss << indentStr.c_str() << "Available Extensions: (" << instanceExtensionCount << ")" << std::endl;
for (const XrExtensionProperties& extension : extensions) {
ss << indentStr << " Name=" << std::string(extension.extensionName) << " SpecVersion=" << extension.extensionVersion << std::endl;
}
Log(Debug::Verbose) << ss.str();
};
// Log non-layer extensions (layerName==nullptr).
logExtensions(nullptr);
// Log layers and any of their extensions.
{
uint32_t layerCount;
CHECK_XRCMD(xrEnumerateApiLayerProperties(0, &layerCount, nullptr));
std::vector<XrApiLayerProperties> layers(layerCount);
for (XrApiLayerProperties& layer : layers) {
layer.type = XR_TYPE_API_LAYER_PROPERTIES;
}
CHECK_XRCMD(xrEnumerateApiLayerProperties((uint32_t)layers.size(), &layerCount, layers.data()));
std::stringstream ss;
ss << "Available Layers: (" << layerCount << ")" << std::endl;
for (const XrApiLayerProperties& layer : layers) {
ss << " Name=" << layer.layerName << " SpecVersion=" << layer.layerVersion << std::endl;
logExtensions(layer.layerName, 2);
}
Log(Debug::Verbose) << ss.str();
}
}
void
OpenXRManagerImpl::LogInstanceInfo() {
XrInstanceProperties instanceProperties{ XR_TYPE_INSTANCE_PROPERTIES };
xrGetInstanceProperties(mInstance, &instanceProperties);
std::stringstream ss;
ss << "Instance RuntimeName=" << instanceProperties.runtimeName << " RuntimeVersion=" << instanceProperties.runtimeVersion;
Log(Debug::Verbose) << ss.str();
}
void
OpenXRManagerImpl::LogReferenceSpaces() {
uint32_t spaceCount;
xrEnumerateReferenceSpaces(mSession, 0, &spaceCount, nullptr);
std::vector<XrReferenceSpaceType> spaces(spaceCount);
xrEnumerateReferenceSpaces(mSession, spaceCount, &spaceCount, spaces.data());
std::stringstream ss;
ss << "Available reference spaces=" << spaceCount << std::endl;
for (XrReferenceSpaceType space : spaces)
ss << " Name: " << to_string(space) << std::endl;
Log(Debug::Verbose) << ss.str();
}
void
OpenXRManagerImpl::waitFrame()
{
if (!mSessionRunning)
return;
XrFrameWaitInfo frameWaitInfo{ XR_TYPE_FRAME_WAIT_INFO };
XrFrameState frameState{ XR_TYPE_FRAME_STATE };
CHECK_XRCMD(xrWaitFrame(mSession, &frameWaitInfo, &frameState));
mFrameState = frameState;
}
void
OpenXRManagerImpl::beginFrame()
{
if (!mSessionRunning)
return;
XrFrameBeginInfo frameBeginInfo{ XR_TYPE_FRAME_BEGIN_INFO };
CHECK_XRCMD(xrBeginFrame(mSession, &frameBeginInfo));
}
void
OpenXRManagerImpl::endFrame()
{
if (!mSessionRunning)
return;
XrFrameEndInfo frameEndInfo{ XR_TYPE_FRAME_END_INFO };
frameEndInfo.displayTime = mFrameState.predictedDisplayTime;
frameEndInfo.environmentBlendMode = mEnvironmentBlendMode;
frameEndInfo.layerCount = (uint32_t)1;
frameEndInfo.layers = &mLayer_p;
CHECK_XRCMD(xrEndFrame(mSession, &frameEndInfo));
}
std::array<XrView, 2> OpenXRManagerImpl::getStageViews()
{
// Get the pose of each eye in the "stage" reference space.
// TODO: I likely won't ever use this, since it is only useful if the game world and the
// stage space have matching orientation, which is extremely unlikely. Instead we have
// to keep track of the head pose separately and move our world position based on progressive
// changes.
// In more detail:
// When we apply yaw to the game world to allow free rotation of the character, the orientation
// of the stage space and our world space deviates which breaks free movement.
//
// If we align the orientations by yawing the head pose, that yaw will happen around the origin
// of the stage space rather than the character, which will not be comfortable (or make any sense) to the player.
//
// If we align the orientations by yawing the view poses, the yaw will happen around the character
// as intended, but physically walking will move the player in the wrong direction.
//
// The solution that solves both problems is to yaw the view pose *and* progressively track changes in head pose
// in the stage space and yaw that change before adding it to our separately tracked pose.
std::array<XrView, 2> views{ {{XR_TYPE_VIEW}, {XR_TYPE_VIEW}} };
XrViewState viewState{ XR_TYPE_VIEW_STATE };
uint32_t viewCount = 2;
XrViewLocateInfo viewLocateInfo{ XR_TYPE_VIEW_LOCATE_INFO };
viewLocateInfo.viewConfigurationType = mViewConfigType;
viewLocateInfo.displayTime = mFrameState.predictedDisplayTime;
viewLocateInfo.space = mReferenceSpaceStage;
CHECK_XRCMD(xrLocateViews(mSession, &viewLocateInfo, &viewState, viewCount, &viewCount, views.data()));
return views;
}
std::array<XrView, 2> OpenXRManagerImpl::getHmdViews()
{
// Eye poses relative to the HMD rarely change. But they might
// if the user reconfigures his or her hmd during runtime, so we
// re-read this every frame anyway.
std::array<XrView, 2> views{ {{XR_TYPE_VIEW}, {XR_TYPE_VIEW}} };
XrViewState viewState{ XR_TYPE_VIEW_STATE };
uint32_t viewCount = 2;
XrViewLocateInfo viewLocateInfo{ XR_TYPE_VIEW_LOCATE_INFO };
viewLocateInfo.viewConfigurationType = mViewConfigType;
viewLocateInfo.displayTime = mFrameState.predictedDisplayTime;
viewLocateInfo.space = mReferenceSpaceView;
CHECK_XRCMD(xrLocateViews(mSession, &viewLocateInfo, &viewState, viewCount, &viewCount, views.data()));
return views;
}
XrSpaceLocation OpenXRManagerImpl::getHeadLocation()
{
// The pose of the "View" reference space is the pose of the HMD.
XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION };
CHECK_XRCMD(xrLocateSpace(mReferenceSpaceView, mReferenceSpaceStage, mFrameState.predictedDisplayTime, &location));
return location;
}
int OpenXRManagerImpl::eyes()
{
return mConfigViews.size();
}
void OpenXRManagerImpl::handleEvents()
{
// React to events
while (auto* event = nextEvent())
{
Log(Debug::Verbose) << "OpenXR: Event received: " << to_string(event->type);
switch (event->type)
{
case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED:
{
const auto* stateChangeEvent = reinterpret_cast<const XrEventDataSessionStateChanged*>(event);
HandleSessionStateChanged(*stateChangeEvent);
break;
}
case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING:
case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED:
case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING:
default: {
Log(Debug::Verbose) << "OpenXR: Event ignored";
break;
}
}
}
}
void OpenXRManagerImpl::updateControls()
{
}
void
OpenXRManagerImpl::HandleSessionStateChanged(
const XrEventDataSessionStateChanged& stateChangedEvent)
{
auto oldState = mSessionState;
auto newState = stateChangedEvent.state;
mSessionState = newState;
Log(Debug::Verbose) << "XrEventDataSessionStateChanged: state " << to_string(oldState) << "->" << to_string(newState);
switch (newState)
{
case XR_SESSION_STATE_READY:
case XR_SESSION_STATE_IDLE:
{
XrSessionBeginInfo beginInfo{ XR_TYPE_SESSION_BEGIN_INFO };
beginInfo.primaryViewConfigurationType = mViewConfigType;
CHECK_XRCMD(xrBeginSession(mSession, &beginInfo));
mSessionRunning = true;
break;
}
case XR_SESSION_STATE_STOPPING:
{
mSessionRunning = false;
CHECK_XRCMD(xrEndSession(mSession));
break;
}
default:
Log(Debug::Verbose) << "XrEventDataSessionStateChanged: Ignoring new strate " << to_string(newState);
}
}
void
OpenXRManagerImpl::setViewSubImage(
int eye,
const ::XrSwapchainSubImage& subImage)
{
if (eye >= mProjectionLayerViews.size())
throw std::out_of_range("OpenXRManagerImpl::setViewSubImage: Eye index out of range");
mProjectionLayerViews[eye].subImage = subImage;
}
static void
poseCallbacks(
const XrPosef& newPose,
const XrPosef& oldPose,
OpenXRManager::PoseUpdateCallback* absoluteCB,
OpenXRManager::PoseUpdateCallback* relativeCB
)
{
osg::Quat quat = osg::Quat(
newPose.orientation.x,
newPose.orientation.y,
newPose.orientation.z,
newPose.orientation.w
);
osg::Vec3 oldPos = osg::Vec3(
oldPose.position.x,
oldPose.position.y,
oldPose.position.z
);
osg::Vec3 newPos = osg::Vec3(
oldPose.position.x,
oldPose.position.y,
oldPose.position.z
);
if (absoluteCB)
(*absoluteCB)(newPos, quat);
if (relativeCB)
(*relativeCB)(newPos - oldPos, quat);
}
void OpenXRManagerImpl::updatePoses()
{
auto newHeadTrackedPose = getHeadLocation();
poseCallbacks(newHeadTrackedPose.pose, mHeadTrackedPose, mHeadAbsoluteCB, mHeadRelativeCB);
mHeadTrackedPose = newHeadTrackedPose.pose;
auto stageViews = getStageViews();
mProjectionLayerViews[0].pose = stageViews[0].pose;
mProjectionLayerViews[1].pose = stageViews[1].pose;
mProjectionLayerViews[0].fov = stageViews[0].fov;
mProjectionLayerViews[1].fov = stageViews[1].fov;
}
void OpenXRManagerImpl::setPoseUpdateCallback(
OpenXRManager::PoseUpdateCallback::TrackedLimb limb,
OpenXRManager::PoseUpdateCallback::TrackingMode mode,
osg::ref_ptr<OpenXRManager::PoseUpdateCallback> cb)
{
// This can clearly be made more generic if that ever becomes relevant
switch (limb) {
case OpenXRManager::PoseUpdateCallback::HEAD:
if (mode == OpenXRManager::PoseUpdateCallback::STAGE_ABSOLUTE)
mHeadAbsoluteCB = cb;
else
mHeadRelativeCB = cb;
break;
case OpenXRManager::PoseUpdateCallback::LEFT_HAND:
if (mode == OpenXRManager::PoseUpdateCallback::STAGE_ABSOLUTE)
mLeftHandAbsoluteCB = cb;
else
mLeftHandRelativeCB = cb;
break;
case OpenXRManager::PoseUpdateCallback::RIGHT_HAND:
if (mode == OpenXRManager::PoseUpdateCallback::STAGE_ABSOLUTE)
mRightHandAbsoluteCB = cb;
else
mRightHandRelativeCB = cb;
break;
}
}
const XrEventDataBaseHeader*
OpenXRManagerImpl::nextEvent()
{
XrEventDataBaseHeader* baseHeader = reinterpret_cast<XrEventDataBaseHeader*>(&mEventDataBuffer);
*baseHeader = { XR_TYPE_EVENT_DATA_BUFFER };
const XrResult result = xrPollEvent(mInstance, &mEventDataBuffer);
if (result == XR_SUCCESS)
{
if (baseHeader->type == XR_TYPE_EVENT_DATA_EVENTS_LOST) {
const XrEventDataEventsLost* const eventsLost = reinterpret_cast<const XrEventDataEventsLost*>(baseHeader);
Log(Debug::Warning) << "OpenXRManagerImpl: Lost " << eventsLost->lostEventCount << " events";
}
return baseHeader;
}
if (result != XR_EVENT_UNAVAILABLE)
CHECK_XRRESULT(result, "xrPollEvent");
return nullptr;
}
}

View file

@ -0,0 +1,94 @@
#ifndef OPENXR_MANAGER_IMPL_HPP
#define OPENXR_MANAGER_IMPL_HPP
#include "openxrmanager.hpp"
#include "../mwinput/inputmanagerimp.hpp"
#include <components/debug/debuglog.hpp>
#include <components/sdlutil/sdlgraphicswindow.hpp>
// The OpenXR SDK assumes we've included Windows.h
#include <Windows.h>
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#include <openxr/openxr_platform_defines.h>
#include <openxr/openxr_reflection.h>
#include <vector>
#include <array>
#include <map>
#include <iostream>
namespace MWVR
{
#define CHK_STRINGIFY(x) #x
#define TOSTRING(x) CHK_STRINGIFY(x)
#define FILE_AND_LINE __FILE__ ":" TOSTRING(__LINE__)
#define CHECK_XRCMD(cmd) CheckXrResult(cmd, #cmd, FILE_AND_LINE);
#define CHECK_XRRESULT(res, cmdStr) CheckXrResult(res, cmdStr, FILE_AND_LINE);
XrResult CheckXrResult(XrResult res, const char* originator = nullptr, const char* sourceLocation = nullptr);
struct OpenXRManagerImpl
{
OpenXRManagerImpl(void);
~OpenXRManagerImpl(void);
void LogLayersAndExtensions();
void LogInstanceInfo();
void LogReferenceSpaces();
const XrEventDataBaseHeader* nextEvent();
void waitFrame();
void beginFrame();
void endFrame();
std::array<XrView, 2> getStageViews();
std::array<XrView, 2> getHmdViews();
XrSpaceLocation getHeadLocation();
int eyes();
void handleEvents();
void updateControls();
void updatePoses();
void setPoseUpdateCallback(OpenXRManager::PoseUpdateCallback::TrackedLimb limb, OpenXRManager::PoseUpdateCallback::TrackingMode mode, osg::ref_ptr<OpenXRManager::PoseUpdateCallback> cb);
void HandleSessionStateChanged(const XrEventDataSessionStateChanged& stateChangedEvent);
void setViewSubImage(int eye, const ::XrSwapchainSubImage& subImage);
bool initialized = false;
long long frameIndex = 0;
XrInstance mInstance = XR_NULL_HANDLE;
XrSession mSession = XR_NULL_HANDLE;
XrSpace mSpace = XR_NULL_HANDLE;
XrFormFactor mFormFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
XrViewConfigurationType mViewConfigType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
XrEnvironmentBlendMode mEnvironmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
XrSystemId mSystemId = XR_NULL_SYSTEM_ID;
XrGraphicsBindingOpenGLWin32KHR mGraphicsBinding{ XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR };
XrSystemProperties mSystemProperties{ XR_TYPE_SYSTEM_PROPERTIES };
std::array<XrViewConfigurationView, 2> mConfigViews{ { {XR_TYPE_VIEW_CONFIGURATION_VIEW}, {XR_TYPE_VIEW_CONFIGURATION_VIEW} } };
XrSpace mReferenceSpaceView = XR_NULL_HANDLE;
XrSpace mReferenceSpaceStage = XR_NULL_HANDLE;
XrEventDataBuffer mEventDataBuffer{ XR_TYPE_EVENT_DATA_BUFFER };
XrCompositionLayerProjection mLayer{ XR_TYPE_COMPOSITION_LAYER_PROJECTION };
XrCompositionLayerBaseHeader const* mLayer_p = reinterpret_cast<XrCompositionLayerBaseHeader*>(&mLayer);
std::array<XrCompositionLayerProjectionView, 2> mProjectionLayerViews{ { {XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW}, {XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW} } };
XrFrameState mFrameState{ XR_TYPE_FRAME_STATE };
XrSessionState mSessionState = XR_SESSION_STATE_UNKNOWN;
bool mSessionRunning = false;
XrPosef mHeadTrackedPose{};
XrPosef mLeftHandTrackedPose{};
XrPosef mRightHandTrackedPose{};
osg::ref_ptr<OpenXRManager::PoseUpdateCallback> mHeadAbsoluteCB = nullptr;
osg::ref_ptr<OpenXRManager::PoseUpdateCallback> mHeadRelativeCB = nullptr;
osg::ref_ptr<OpenXRManager::PoseUpdateCallback> mLeftHandAbsoluteCB = nullptr;
osg::ref_ptr<OpenXRManager::PoseUpdateCallback> mLeftHandRelativeCB = nullptr;
osg::ref_ptr<OpenXRManager::PoseUpdateCallback> mRightHandAbsoluteCB = nullptr;
osg::ref_ptr<OpenXRManager::PoseUpdateCallback> mRightHandRelativeCB = nullptr;
};
}
#endif

View file

@ -0,0 +1,172 @@
#include "openxrviewer.hpp"
#include "openxrtexture.hpp"
#include <osg/Texture2D>
#include <osgViewer/Renderer>
#include <components/debug/debuglog.hpp>
#include <osgDB/Registry>
#include <sstream>
#include <fstream>
#ifndef GL_TEXTURE_MAX_LEVEL
#define GL_TEXTURE_MAX_LEVEL 0x813D
#endif
namespace MWVR
{
OpenXRTextureBuffer::OpenXRTextureBuffer(
osg::ref_ptr<OpenXRManager> XR,
osg::ref_ptr<osg::State> state,
uint32_t XRColorBuffer,
std::size_t width,
std::size_t height,
uint32_t msaaSamples)
: mXR(XR)
, mState(state)
, mWidth(width)
, mHeight(height)
, mXRColorBuffer(XRColorBuffer)
, mMSAASamples(msaaSamples)
{
auto* gl = osg::GLExtensions::Get(state->getContextID(), false);
glBindTexture(GL_TEXTURE_2D, XRColorBuffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
GLint w = 0;
GLint h = 0;
glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WIDTH, &w);
glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_HEIGHT, &h);
gl->glGenFramebuffers(1, &mFBO);
if (mMSAASamples == 0)
{
glGenTextures(1, &mDepthBuffer);
glBindTexture(GL_TEXTURE_2D, mDepthBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, mWidth, mHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, mFBO);
gl->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, mXRColorBuffer, 0);
gl->glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, mDepthBuffer, 0);
if (gl->glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT)
throw std::runtime_error("Failed to create OpenXR framebuffer");
}
else
{
gl->glGenFramebuffers(1, &mMSAAFBO);
// Create MSAA color buffer
glGenTextures(1, &mMSAAColorBuffer);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mMSAAColorBuffer);
gl->glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, mMSAASamples, GL_RGBA, mWidth, mHeight, false);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER_ARB);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER_ARB);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MAX_LEVEL, 0);
// Create MSAA depth buffer
glGenTextures(1, &mMSAADepthBuffer);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mMSAADepthBuffer);
gl->glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, mMSAASamples, GL_DEPTH_COMPONENT, mWidth, mHeight, false);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MAX_LEVEL, 0);
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, mMSAAFBO);
gl->glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D_MULTISAMPLE, mMSAAColorBuffer, 0);
gl->glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D_MULTISAMPLE, mMSAADepthBuffer, 0);
if (gl->glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT)
throw std::runtime_error("Failed to create MSAA framebuffer");
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, mFBO);
gl->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, mXRColorBuffer, 0);
gl->glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, 0);
if (gl->glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT)
throw std::runtime_error("Failed to create OpenXR framebuffer");
}
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
}
OpenXRTextureBuffer::~OpenXRTextureBuffer()
{
destroy(nullptr);
}
void OpenXRTextureBuffer::destroy(osg::State* state)
{
if (!state)
{
// Try re-using the state received during construction
state = mState.get();
}
if (state)
{
auto* gl = osg::GLExtensions::Get(state->getContextID(), false);
if (mFBO)
gl->glDeleteFramebuffers(1, &mFBO);
if (mMSAAFBO)
gl->glDeleteFramebuffers(1, &mMSAAFBO);
}
else if(mFBO || mMSAAFBO)
// Without access to opengl methods, i'll just let the FBOs leak.
Log(Debug::Warning) << "destroy() called without a State. Leaking FBOs";
if (mDepthBuffer)
glDeleteTextures(1, &mDepthBuffer);
if (mMSAAColorBuffer)
glDeleteTextures(1, &mMSAAColorBuffer);
if (mMSAADepthBuffer)
glDeleteTextures(1, &mMSAADepthBuffer);
mFBO = mMSAAFBO = mDepthBuffer = mMSAAColorBuffer = mMSAADepthBuffer = 0;
}
void OpenXRTextureBuffer::beginFrame(osg::RenderInfo& renderInfo)
{
auto state = renderInfo.getState();
auto* gl = osg::GLExtensions::Get(state->getContextID(), false);
if (mMSAASamples == 0)
{
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, mFBO);
}
else
{
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, mMSAAFBO);
}
}
void OpenXRTextureBuffer::endFrame(osg::RenderInfo& renderInfo)
{
auto* state = renderInfo.getState();
auto* gl = osg::GLExtensions::Get(state->getContextID(), false);
if (mMSAASamples == 0)
{
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
}
else
{
gl->glBindFramebuffer(GL_READ_FRAMEBUFFER_EXT, mMSAAFBO);
gl->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, mFBO);
gl->glBlitFramebuffer(0, 0, mWidth, mHeight, 0, 0, mWidth, mHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
gl->glBindFramebuffer(GL_READ_FRAMEBUFFER_EXT, 0);
}
}
}

View file

@ -0,0 +1,55 @@
#ifndef MWVR_OPENRXTEXTURE_H
#define MWVR_OPENRXTEXTURE_H
#include <memory>
#include <osg/Group>
#include <osg/Camera>
#include <osgViewer/Viewer>
#include "openxrmanager.hpp"
namespace MWVR
{
class OpenXRTextureBuffer : public osg::Referenced
{
public:
OpenXRTextureBuffer(osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<osg::State> state, uint32_t XRColorBuffer, std::size_t width, std::size_t height, uint32_t msaaSamples);
~OpenXRTextureBuffer();
void destroy(osg::State* state);
auto width() const { return mWidth; }
auto height() const { return mHeight; }
auto msaaSamples() const { return mMSAASamples; }
void beginFrame(osg::RenderInfo& renderInfo);
void endFrame(osg::RenderInfo& renderInfo);
void writeToJpg(osg::State& state, std::string filename);
private:
osg::observer_ptr<OpenXRManager> mXR;
// Set aside a weak pointer to the constructor state to use when freeing FBOs, if no state is given to destroy()
osg::observer_ptr<osg::State> mState;
// Metadata
std::size_t mWidth = 0;
std::size_t mHeight = 0;
// Swapchain buffer
uint32_t mXRColorBuffer = 0;
// FBO target for swapchain buffer
uint32_t mDepthBuffer = 0;
uint32_t mFBO = 0;
// Render targets for MSAA
uint32_t mMSAASamples = 0;
uint32_t mMSAAFBO = 0;
uint32_t mMSAAColorBuffer = 0;
uint32_t mMSAADepthBuffer = 0;
};
}
#endif

View file

@ -0,0 +1,375 @@
#include "openxrview.hpp"
#include "openxrmanager.hpp"
#include "openxrmanagerimpl.hpp"
#include "../mwinput/inputmanagerimp.hpp"
#include <components/debug/debuglog.hpp>
#include <components/sdlutil/sdlgraphicswindow.hpp>
#include <Windows.h>
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#include <openxr/openxr_platform_defines.h>
#include <openxr/openxr_reflection.h>
#include <osg/Camera>
#include <osgViewer/Renderer>
#include <vector>
#include <array>
#include <iostream>
namespace osg {
Quat fromXR(XrQuaternionf quat)
{
return Quat{ quat.x, quat.y, quat.z, quat.w };
}
}
namespace MWVR
{
class OpenXRViewImpl
{
public:
enum SubView
{
LEFT_VIEW = 0,
RIGHT_VIEW = 1,
SUBVIEW_MAX = RIGHT_VIEW, //!< Used to size subview arrays. Not a valid input.
};
OpenXRViewImpl(osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<osg::State> state, float metersPerUnit, unsigned int viewIndex);
~OpenXRViewImpl();
osg::ref_ptr<OpenXRTextureBuffer> prepareNextSwapchainImage();
void releaseSwapchainImage();
void prerenderCallback(osg::RenderInfo& renderInfo);
void postrenderCallback(osg::RenderInfo& renderInfo);
osg::Camera* createCamera(int eye, const osg::Vec4& clearColor, osg::GraphicsContext* gc);
osg::Matrix projectionMatrix();
osg::Matrix viewMatrix();
osg::ref_ptr<OpenXRManager> mXR;
XrSwapchain mSwapchain = XR_NULL_HANDLE;
std::vector<XrSwapchainImageOpenGLKHR> mSwapchainImageBuffers{};
std::vector<osg::ref_ptr<OpenXRTextureBuffer> > mTextureBuffers{};
int32_t mWidth = -1;
int32_t mHeight = -1;
int64_t mSwapchainColorFormat = -1;
XrViewConfigurationView mConfig{ XR_TYPE_VIEW_CONFIGURATION_VIEW };
XrSwapchainSubImage mSubImage{};
OpenXRTextureBuffer* mCurrentBuffer = nullptr;
float mMetersPerUnit = 1.f;
int mViewIndex = -1;
};
OpenXRViewImpl::OpenXRViewImpl(
osg::ref_ptr<OpenXRManager> XR,
osg::ref_ptr<osg::State> state,
float metersPerUnit,
unsigned int viewIndex)
: mXR(XR)
, mConfig()
, mMetersPerUnit(metersPerUnit)
, mViewIndex(viewIndex)
{
auto& pXR = *(mXR->mPrivate);
if (viewIndex >= pXR.mConfigViews.size())
throw std::logic_error("viewIndex out of range");
mConfig = pXR.mConfigViews[viewIndex];
// Select a swapchain format.
uint32_t swapchainFormatCount;
CHECK_XRCMD(xrEnumerateSwapchainFormats(pXR.mSession, 0, &swapchainFormatCount, nullptr));
std::vector<int64_t> swapchainFormats(swapchainFormatCount);
CHECK_XRCMD(xrEnumerateSwapchainFormats(pXR.mSession, (uint32_t)swapchainFormats.size(), &swapchainFormatCount, swapchainFormats.data()));
// List of supported color swapchain formats.
constexpr int64_t SupportedColorSwapchainFormats[] = {
GL_RGBA8,
GL_RGBA8_SNORM,
};
auto swapchainFormatIt =
std::find_first_of(swapchainFormats.begin(), swapchainFormats.end(), std::begin(SupportedColorSwapchainFormats),
std::end(SupportedColorSwapchainFormats));
if (swapchainFormatIt == swapchainFormats.end()) {
Log(Debug::Error) << "No swapchain format supported at runtime";
}
mSwapchainColorFormat = *swapchainFormatIt;
Log(Debug::Verbose) << "Creating swapchain with dimensions Width=" << mConfig.recommendedImageRectWidth << " Heigh=" << mConfig.recommendedImageRectHeight << " SampleCount=" << mConfig.recommendedSwapchainSampleCount;
// Create the swapchain.
XrSwapchainCreateInfo swapchainCreateInfo{ XR_TYPE_SWAPCHAIN_CREATE_INFO };
swapchainCreateInfo.arraySize = 1;
swapchainCreateInfo.format = mSwapchainColorFormat;
swapchainCreateInfo.width = mConfig.recommendedImageRectWidth;
swapchainCreateInfo.height = mConfig.recommendedImageRectHeight;
swapchainCreateInfo.mipCount = 1;
swapchainCreateInfo.faceCount = 1;
swapchainCreateInfo.sampleCount = mConfig.recommendedSwapchainSampleCount;
swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
mWidth = mConfig.recommendedImageRectWidth;
mHeight = mConfig.recommendedImageRectHeight;
CHECK_XRCMD(xrCreateSwapchain(pXR.mSession, &swapchainCreateInfo, &mSwapchain));
uint32_t imageCount = 0;
CHECK_XRCMD(xrEnumerateSwapchainImages(mSwapchain, 0, &imageCount, nullptr));
mSwapchainImageBuffers.resize(imageCount, { XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR });
CHECK_XRCMD(xrEnumerateSwapchainImages(mSwapchain, imageCount, &imageCount, reinterpret_cast<XrSwapchainImageBaseHeader*>(mSwapchainImageBuffers.data())));
for (const auto& swapchainImage : mSwapchainImageBuffers)
mTextureBuffers.push_back(new OpenXRTextureBuffer(mXR, state, swapchainImage.image, mWidth, mHeight, 0));
mSubImage.swapchain = mSwapchain;
mSubImage.imageRect.offset = { 0, 0 };
mSubImage.imageRect.extent = { mWidth, mHeight };
mXR->setViewSubImage(viewIndex, mSubImage);
}
OpenXRViewImpl::~OpenXRViewImpl()
{
if (mSwapchain)
CHECK_XRCMD(xrDestroySwapchain(mSwapchain));
}
osg::ref_ptr<OpenXRTextureBuffer>
OpenXRViewImpl::prepareNextSwapchainImage()
{
if (!mXR->sessionRunning())
return nullptr;
XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
uint32_t swapchainImageIndex = 0;
CHECK_XRCMD(xrAcquireSwapchainImage(mSwapchain, &acquireInfo, &swapchainImageIndex));
XrSwapchainImageWaitInfo waitInfo{ XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO };
waitInfo.timeout = XR_INFINITE_DURATION;
CHECK_XRCMD(xrWaitSwapchainImage(mSwapchain, &waitInfo));
return mTextureBuffers[swapchainImageIndex];
}
void
OpenXRViewImpl::releaseSwapchainImage()
{
if (!mXR->sessionRunning())
return;
XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
CHECK_XRCMD(xrReleaseSwapchainImage(mSwapchain, &releaseInfo));
}
void OpenXRViewImpl::prerenderCallback(osg::RenderInfo& renderInfo)
{
mCurrentBuffer = prepareNextSwapchainImage();
if(mCurrentBuffer)
mCurrentBuffer->beginFrame(renderInfo);
}
void OpenXRViewImpl::postrenderCallback(osg::RenderInfo& renderInfo)
{
if (mCurrentBuffer)
mCurrentBuffer->endFrame(renderInfo);
releaseSwapchainImage();
}
// Some headers like to define these.
#ifdef near
#undef near
#endif
#ifdef far
#undef far
#endif
static osg::Matrix
perspectiveFovMatrix(float near, float far, XrFovf fov)
{
const float tanLeft = tanf(fov.angleLeft);
const float tanRight = tanf(fov.angleRight);
const float tanDown = tanf(fov.angleDown);
const float tanUp = tanf(fov.angleUp);
const float tanWidth = tanRight - tanLeft;
const float tanHeight = tanUp - tanDown;
// Set to nearZ for a [-1,1] Z clip space (OpenGL / OpenGL ES).
// Set to zero for a [0,1] Z clip space (Vulkan / D3D / Metal).
const float offset = near;
float matrix[16] = {};
if (far <= near) {
// place the far plane at infinity
matrix[0] = 2 / tanWidth;
matrix[4] = 0;
matrix[8] = (tanRight + tanLeft) / tanWidth;
matrix[12] = 0;
matrix[1] = 0;
matrix[5] = 2 / tanHeight;
matrix[9] = (tanUp + tanDown) / tanHeight;
matrix[13] = 0;
matrix[2] = 0;
matrix[6] = 0;
matrix[10] = -1;
matrix[14] = -(near + offset);
matrix[3] = 0;
matrix[7] = 0;
matrix[11] = -1;
matrix[15] = 0;
}
else {
// normal projection
matrix[0] = 2 / tanWidth;
matrix[4] = 0;
matrix[8] = (tanRight + tanLeft) / tanWidth;
matrix[12] = 0;
matrix[1] = 0;
matrix[5] = 2 / tanHeight;
matrix[9] = (tanUp + tanDown) / tanHeight;
matrix[13] = 0;
matrix[2] = 0;
matrix[6] = 0;
matrix[10] = -(far + offset) / (far - near);
matrix[14] = -(far * (near + offset)) / (far - near);
matrix[3] = 0;
matrix[7] = 0;
matrix[11] = -1;
matrix[15] = 0;
}
return osg::Matrix(matrix);
}
osg::Matrix OpenXRViewImpl::projectionMatrix()
{
auto hmdViews = mXR->mPrivate->getHmdViews();
float near = Settings::Manager::getFloat("near clip", "Camera");
float far = Settings::Manager::getFloat("viewing distance", "Camera");
//return perspectiveFovMatrix()
return perspectiveFovMatrix(near, far, hmdViews[mViewIndex].fov);
}
osg::Matrix OpenXRViewImpl::viewMatrix()
{
osg::Matrix viewMatrix;
auto hmdViews = mXR->mPrivate->getHmdViews();
auto pose = hmdViews[mViewIndex].pose;
osg::Vec3 position = osg::Vec3(pose.position.x, pose.position.y, pose.position.z);
// invert orientation (conjugate of Quaternion) and position to apply to the view matrix as offset
// TODO: Why invert/conjugate?
viewMatrix.setTrans(position);
viewMatrix.postMultRotate(osg::fromXR(pose.orientation));
// Scale to world units
viewMatrix.postMultScale(osg::Vec3d(mMetersPerUnit, mMetersPerUnit, mMetersPerUnit));
return viewMatrix;
}
osg::Camera* OpenXRViewImpl::createCamera(int eye, const osg::Vec4& clearColor, osg::GraphicsContext* gc)
{
osg::ref_ptr<osg::Camera> camera = new osg::Camera();
camera->setClearColor(clearColor);
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
camera->setRenderOrder(osg::Camera::PRE_RENDER, eye);
camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
camera->setAllowEventFocus(false);
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
camera->setViewport(0, 0, mWidth, mHeight);
camera->setGraphicsContext(gc);
camera->setInitialDrawCallback(new OpenXRView::InitialDrawCallback());
camera->setPreDrawCallback(new OpenXRView::PredrawCallback(camera.get(), this));
camera->setFinalDrawCallback(new OpenXRView::PostdrawCallback(camera.get(), this));
return camera.release();
}
OpenXRView::OpenXRView(
osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<osg::State> state, float metersPerUnit, unsigned int viewIndex)
: mPrivate(new OpenXRViewImpl(XR, state, metersPerUnit, viewIndex))
{
}
OpenXRView::~OpenXRView()
{
}
//! Get the next color buffer
osg::ref_ptr<OpenXRTextureBuffer> OpenXRView::prepareNextSwapchainImage()
{
return mPrivate->prepareNextSwapchainImage();
}
//! Release current color buffer. Do not forget to call this after rendering to the color buffer.
void OpenXRView::releaseSwapchainImage()
{
mPrivate->releaseSwapchainImage();
}
void OpenXRView::PredrawCallback::operator()(osg::RenderInfo& info) const
{
mView->prerenderCallback(info);
}
void OpenXRView::PostdrawCallback::operator()(osg::RenderInfo& info) const
{
mView->postrenderCallback(info);
}
void OpenXRView::prerenderCallback(osg::RenderInfo& renderInfo)
{
mPrivate->prerenderCallback(renderInfo);
}
void OpenXRView::postrenderCallback(osg::RenderInfo& renderInfo)
{
mPrivate->postrenderCallback(renderInfo);
}
osg::Camera* OpenXRView::createCamera(int eye, const osg::Vec4& clearColor, osg::GraphicsContext* gc)
{
return mPrivate->createCamera(eye, clearColor, gc);
}
osg::Matrix OpenXRView::projectionMatrix()
{
return mPrivate->projectionMatrix();
}
osg::Matrix OpenXRView::viewMatrix()
{
return mPrivate->viewMatrix();
}
void OpenXRView::InitialDrawCallback::operator()(osg::RenderInfo& renderInfo) const
{
osg::GraphicsOperation* graphicsOperation = renderInfo.getCurrentCamera()->getRenderer();
osgViewer::Renderer* renderer = dynamic_cast<osgViewer::Renderer*>(graphicsOperation);
if (renderer != nullptr)
{
// Disable normal OSG FBO camera setup because it will undo the MSAA FBO configuration.
renderer->setCameraRequiresSetUp(false);
}
}
}

View file

@ -0,0 +1,77 @@
#ifndef OPENXR_VIEW_HPP
#define OPENXR_VIEW_HPP
#include "openxrmanager.hpp"
#include "openxrtexture.hpp"
namespace MWVR
{
class OpenXRViewImpl;
class OpenXRView : public osg::Referenced
{
public:
class PredrawCallback : public osg::Camera::DrawCallback
{
public:
PredrawCallback(osg::Camera* camera, OpenXRViewImpl* view)
: mCamera(camera)
, mView(view)
{}
void operator()(osg::RenderInfo& info) const override;
private:
osg::observer_ptr<osg::Camera> mCamera;
OpenXRViewImpl* mView;
};
class PostdrawCallback : public osg::Camera::DrawCallback
{
public:
PostdrawCallback(osg::Camera* camera, OpenXRViewImpl* view)
: mCamera(camera)
, mView(view)
{}
void operator()(osg::RenderInfo& info) const override;
private:
osg::observer_ptr<osg::Camera> mCamera;
OpenXRViewImpl* mView;
};
class InitialDrawCallback : public osg::Camera::DrawCallback
{
public:
virtual void operator()(osg::RenderInfo& renderInfo) const;
};
public:
OpenXRView(osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<osg::State> state, float metersPerUnit, unsigned int viewIndex);
~OpenXRView();
//! Get the next color buffer.
//! \return The GL texture ID of the now current swapchain image
osg::ref_ptr<OpenXRTextureBuffer> prepareNextSwapchainImage();
//! Release current color buffer. Do not forget to call this after rendering to the color buffer.
void releaseSwapchainImage();
//! Prepare for render
void prerenderCallback(osg::RenderInfo& renderInfo);
//! Finalize render
void postrenderCallback(osg::RenderInfo& renderInfo);
//! Create camera for this view
osg::Camera* createCamera(int eye, const osg::Vec4& clearColor, osg::GraphicsContext* gc);
//! Projection offset for this view
osg::Matrix projectionMatrix();
//! View offset for this view
osg::Matrix viewMatrix();
std::unique_ptr<OpenXRViewImpl> mPrivate;
};
}
#endif

View file

@ -0,0 +1,126 @@
#include "openxrviewer.hpp"
#include "openxrmanagerimpl.hpp"
#include "Windows.h"
namespace MWVR
{
OpenXRViewer::OpenXRViewer(
osg::ref_ptr<OpenXRManager> XR,
osg::ref_ptr<OpenXRManager::RealizeOperation> realizeOperation,
osg::ref_ptr<osgViewer::Viewer> viewer,
float metersPerUnit)
: mXR(XR)
, mRealizeOperation(realizeOperation)
, mViewer(viewer)
, mMetersPerUnit(metersPerUnit)
, mConfigured(false)
{
}
OpenXRViewer::~OpenXRViewer(void)
{
}
void
OpenXRViewer::traverse(
osg::NodeVisitor& visitor)
{
osg::ref_ptr<OpenXRManager::RealizeOperation> realizeOperation = nullptr;
if (mRealizeOperation.lock(realizeOperation))
if (mRealizeOperation->realized())
if (configure())
osg::Group::traverse(visitor);
}
bool
OpenXRViewer::configure()
{
if (mConfigured)
return true;
auto context = mViewer->getCamera()->getGraphicsContext();
if (!context->makeCurrent())
{
Log(Debug::Warning) << "OpenXRViewer::configure() failed to make graphics context current.";
return false;
}
context->setSwapCallback(new OpenXRManager::SwapBuffersCallback(mXR));
auto DC = wglGetCurrentDC();
auto GLRC = wglGetCurrentContext();
if (DC != mXR->mPrivate->mGraphicsBinding.hDC)
{
Log(Debug::Warning) << "Graphics DC does not match DC used to construct XR context";
}
if (GLRC != mXR->mPrivate->mGraphicsBinding.hGLRC)
{
Log(Debug::Warning) << "Graphics GLRC does not match GLRC used to construct XR context";
}
osg::ref_ptr<osg::Camera> camera = mViewer->getCamera();
camera->setName("Main");
osg::Vec4 clearColor = camera->getClearColor();
mViews[LEFT_VIEW] = new OpenXRView(mXR, context->getState(), mMetersPerUnit, 0);
mViews[RIGHT_VIEW] = new OpenXRView(mXR, context->getState(), mMetersPerUnit, 1);
osg::Camera* leftCamera = mViews[LEFT_VIEW]->createCamera(LEFT_VIEW, clearColor, context);
osg::Camera* rightCamera = mViews[RIGHT_VIEW]->createCamera(RIGHT_VIEW, clearColor, context);
leftCamera->setName("LeftEye");
rightCamera->setName("RightEye");
mViewer->addSlave(leftCamera, mViews[LEFT_VIEW]->projectionMatrix(), mViews[LEFT_VIEW]->viewMatrix(), true);
mViewer->addSlave(rightCamera, mViews[RIGHT_VIEW]->projectionMatrix(), mViews[RIGHT_VIEW]->viewMatrix(), true);
mViewer->getSlave(LEFT_VIEW)._updateSlaveCallback = new UpdateSlaveCallback(mXR, mViews[LEFT_VIEW]);
mViewer->getSlave(RIGHT_VIEW)._updateSlaveCallback = new UpdateSlaveCallback(mXR, mViews[RIGHT_VIEW]);
mViewer->setLightingMode(osg::View::SKY_LIGHT);
mViewer->setReleaseContextAtEndOfFrameHint(false);
// Rendering main camera is a waste of time with VR enabled
//camera->setGraphicsContext(nullptr);
mXRInput.reset(new OpenXRInputManager(mXR));
mConfigured = true;
return true;
}
void
OpenXRViewer::UpdateSlaveCallback::updateSlave(
osg::View& view,
osg::View::Slave& slave)
{
mXR->handleEvents();
if (!mXR->sessionRunning())
return;
auto* camera = slave._camera.get();
auto name = camera->getName();
Log(Debug::Debug) << "Updating camera " << name;
if (camera->getName() == "LeftEye")
{
mXR->handleEvents();
mXR->waitFrame();
mXR->beginFrame();
mXR->updateControls();
mXR->updatePoses();
}
auto viewMatrix = view.getCamera()->getViewMatrix() * mView->viewMatrix();
auto projMatrix = mView->projectionMatrix();
camera->setViewMatrix(viewMatrix);
camera->setProjectionMatrix(projMatrix);
}
}

View file

@ -0,0 +1,67 @@
#ifndef MWVR_OPENRXVIEWER_H
#define MWVR_OPENRXVIEWER_H
#include <memory>
#include <array>
#include <osg/Group>
#include <osg/Camera>
#include <osgViewer/Viewer>
#include "openxrmanager.hpp"
#include "openxrview.hpp"
#include "openxrinputmanager.hpp"
namespace MWVR
{
class OpenXRViewer : public osg::Group
{
public:
class UpdateSlaveCallback : public osg::View::Slave::UpdateSlaveCallback
{
public:
UpdateSlaveCallback(osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<OpenXRView> view)
: mXR(XR), mView(view)
{}
void updateSlave(osg::View& view, osg::View::Slave& slave) override;
private:
osg::ref_ptr<OpenXRManager> mXR;
osg::ref_ptr<OpenXRView> mView;
};
public:
enum Views
{
LEFT_VIEW = 0,
RIGHT_VIEW = 1
};
public:
//! Create an OpenXR manager based on the graphics context from the given window.
//! The OpenXRManager will make its own context with shared resources.
OpenXRViewer(
osg::ref_ptr<OpenXRManager> XR,
osg::ref_ptr<OpenXRManager::RealizeOperation> realizeOperation,
osg::ref_ptr<osgViewer::Viewer> viewer,
float metersPerUnit = 1.f);
~OpenXRViewer(void);
virtual void traverse(osg::NodeVisitor& visitor) override;
protected:
virtual bool configure();
osg::observer_ptr<OpenXRManager> mXR = nullptr;
osg::observer_ptr<OpenXRManager::RealizeOperation> mRealizeOperation = nullptr;
osg::observer_ptr<osgViewer::Viewer> mViewer = nullptr;
std::unique_ptr<MWVR::OpenXRInputManager> mXRInput = nullptr;
std::array<osg::ref_ptr<OpenXRView>, 2> mViews{};
float mMetersPerUnit = 1.f;
bool mConfigured = false;
};
}
#endif

45
cmake/FindOpenXR.cmake Normal file
View file

@ -0,0 +1,45 @@
# Locate OpenXR library
# This module defines
# OpenXR_LIBRARY, the OpenXR library, with no other libraries
# OpenXR_LIBRARIES, the OpenXR library and required components with compiler flags
# OpenXR_FOUND, if false, do not try to link to OpenXR
# OpenXR_INCLUDE_DIR, where to find openxr.h
# OpenXR_VERSION, the version of the found library
#
# This module accepts the following env variables
# OPENXR_ROOT
# This module responds to the the flag:
#=============================================================================
# Copyright 2003-2009 Kitware, Inc.
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
if(WIN32)
if (CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_openxr_default_folder "C:/Program Files/OPENXR")
else()
set(_openxr_default_folder "C:/Program Files (x86)/OPENXR")
endif()
endif(WIN32)
libfind_pkg_detect(OpenXR openxr
FIND_PATH openxr/openxr.h
HINTS $ENV{OPENXR_ROOT} ${_openxr_default_folder}
PATH_SUFFIXES include
FIND_LIBRARY openxr_loader.lib
HINTS $ENV{OPENXR_ROOT} ${_openxr_default_folder}
PATH_SUFFIXES lib lib32 lib64
)
libfind_version_n_header(OpenXR NAMES openxr/openxr.h DEFINES XR_VERSION_MAJOR XR_VERSION_MINOR XR_VERSION_PATCH)
libfind_process(OpenXR)