Add OpenMW VR commits up to 4 Jul 2021
# Conflicts: # CMakeLists.txt # LICENSE # README.md # apps/openmw/CMakeLists.txt # apps/openmw/engine.cpp # apps/openmw/mwclass/creature.cpp # apps/openmw/mwclass/npc.cpp # apps/openmw/mwclass/npc.hpp # apps/openmw/mwinput/bindingsmanager.cpp # apps/openmw/mwmechanics/combat.cpppull/615/head
commit
339a196579
@ -0,0 +1,883 @@
|
|||||||
|
#include "engine.hpp"
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
#include <boost/filesystem/fstream.hpp>
|
||||||
|
|
||||||
|
#include <osgViewer/ViewerEventHandlers>
|
||||||
|
#include <osgDB/ReadFile>
|
||||||
|
#include <osgDB/WriteFile>
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
|
#include <components/misc/rng.hpp>
|
||||||
|
|
||||||
|
#include <components/vfs/manager.hpp>
|
||||||
|
#include <components/vfs/registerarchives.hpp>
|
||||||
|
|
||||||
|
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
||||||
|
#include <components/sdlutil/imagetosurface.hpp>
|
||||||
|
|
||||||
|
#include <components/resource/resourcesystem.hpp>
|
||||||
|
#include <components/resource/scenemanager.hpp>
|
||||||
|
#include <components/resource/stats.hpp>
|
||||||
|
|
||||||
|
#include <components/compiler/extensions0.hpp>
|
||||||
|
|
||||||
|
#include <components/sceneutil/workqueue.hpp>
|
||||||
|
|
||||||
|
#include <components/files/configurationmanager.hpp>
|
||||||
|
|
||||||
|
#include <components/version/version.hpp>
|
||||||
|
|
||||||
|
#include <components/detournavigator/navigator.hpp>
|
||||||
|
|
||||||
|
#include "mwinput/inputmanagerimp.hpp"
|
||||||
|
|
||||||
|
#include "mwgui/windowmanagerimp.hpp"
|
||||||
|
|
||||||
|
#include "mwscript/scriptmanagerimp.hpp"
|
||||||
|
#include "mwscript/interpretercontext.hpp"
|
||||||
|
|
||||||
|
#include "mwsound/soundmanagerimp.hpp"
|
||||||
|
|
||||||
|
#include "mwworld/class.hpp"
|
||||||
|
#include "mwworld/player.hpp"
|
||||||
|
#include "mwworld/worldimp.hpp"
|
||||||
|
|
||||||
|
#include "mwrender/vismask.hpp"
|
||||||
|
|
||||||
|
#include "mwclass/classes.hpp"
|
||||||
|
|
||||||
|
#include "mwdialogue/dialoguemanagerimp.hpp"
|
||||||
|
#include "mwdialogue/journalimp.hpp"
|
||||||
|
#include "mwdialogue/scripttest.hpp"
|
||||||
|
|
||||||
|
#include "mwmechanics/mechanicsmanagerimp.hpp"
|
||||||
|
|
||||||
|
#include "mwstate/statemanagerimp.hpp"
|
||||||
|
|
||||||
|
#ifdef USE_OPENXR
|
||||||
|
#include "mwvr/openxrinputmanager.hpp"
|
||||||
|
#include "mwvr/openxrviewer.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void checkSDLError(int ret)
|
||||||
|
{
|
||||||
|
if (ret != 0)
|
||||||
|
Log(Debug::Error) << "SDL error: " << SDL_GetError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::executeLocalScripts()
|
||||||
|
{
|
||||||
|
MWWorld::LocalScripts& localScripts = mEnvironment.getWorld()->getLocalScripts();
|
||||||
|
|
||||||
|
localScripts.startIteration();
|
||||||
|
std::pair<std::string, MWWorld::Ptr> script;
|
||||||
|
while (localScripts.getNext(script))
|
||||||
|
{
|
||||||
|
MWScript::InterpreterContext interpreterContext (
|
||||||
|
&script.second.getRefData().getLocals(), script.second);
|
||||||
|
mEnvironment.getScriptManager()->run (script.first, interpreterContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OMW::Engine::frame(float frametime)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mStartTick = mViewer->getStartTick();
|
||||||
|
|
||||||
|
mEnvironment.setFrameDuration(frametime);
|
||||||
|
|
||||||
|
// update input
|
||||||
|
mEnvironment.getInputManager()->update(frametime, false);
|
||||||
|
|
||||||
|
// When the window is minimized, pause the game. Currently this *has* to be here to work around a MyGUI bug.
|
||||||
|
// If we are not currently rendering, then RenderItems will not be reused resulting in a memory leak upon changing widget textures (fixed in MyGUI 3.3.2),
|
||||||
|
// and destroyed widgets will not be deleted (not fixed yet, https://github.com/MyGUI/mygui/issues/21)
|
||||||
|
if (!mEnvironment.getWindowManager()->isWindowVisible())
|
||||||
|
{
|
||||||
|
mEnvironment.getSoundManager()->pausePlayback();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
mEnvironment.getSoundManager()->resumePlayback();
|
||||||
|
|
||||||
|
// sound
|
||||||
|
if (mUseSound)
|
||||||
|
mEnvironment.getSoundManager()->update(frametime);
|
||||||
|
|
||||||
|
// Main menu opened? Then scripts are also paused.
|
||||||
|
bool paused = mEnvironment.getWindowManager()->containsMode(MWGui::GM_MainMenu);
|
||||||
|
|
||||||
|
// update game state
|
||||||
|
mEnvironment.getStateManager()->update (frametime);
|
||||||
|
|
||||||
|
bool guiActive = mEnvironment.getWindowManager()->isGuiMode();
|
||||||
|
|
||||||
|
osg::Timer_t beforeScriptTick = osg::Timer::instance()->tick();
|
||||||
|
if (mEnvironment.getStateManager()->getState()!=
|
||||||
|
MWBase::StateManager::State_NoGame)
|
||||||
|
{
|
||||||
|
if (!paused)
|
||||||
|
{
|
||||||
|
if (mEnvironment.getWorld()->getScriptsEnabled())
|
||||||
|
{
|
||||||
|
// local scripts
|
||||||
|
executeLocalScripts();
|
||||||
|
|
||||||
|
// global scripts
|
||||||
|
mEnvironment.getScriptManager()->getGlobalScripts().run();
|
||||||
|
}
|
||||||
|
|
||||||
|
mEnvironment.getWorld()->markCellAsUnchanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!guiActive)
|
||||||
|
{
|
||||||
|
double hours = (frametime * mEnvironment.getWorld()->getTimeScaleFactor()) / 3600.0;
|
||||||
|
mEnvironment.getWorld()->advanceTime(hours, true);
|
||||||
|
mEnvironment.getWorld()->rechargeItems(frametime, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
osg::Timer_t afterScriptTick = osg::Timer::instance()->tick();
|
||||||
|
|
||||||
|
// update actors
|
||||||
|
osg::Timer_t beforeMechanicsTick = osg::Timer::instance()->tick();
|
||||||
|
if (mEnvironment.getStateManager()->getState()!=
|
||||||
|
MWBase::StateManager::State_NoGame)
|
||||||
|
{
|
||||||
|
mEnvironment.getMechanicsManager()->update(frametime,
|
||||||
|
guiActive);
|
||||||
|
}
|
||||||
|
osg::Timer_t afterMechanicsTick = osg::Timer::instance()->tick();
|
||||||
|
|
||||||
|
if (mEnvironment.getStateManager()->getState()==
|
||||||
|
MWBase::StateManager::State_Running)
|
||||||
|
{
|
||||||
|
MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr();
|
||||||
|
if(!guiActive && player.getClass().getCreatureStats(player).isDead())
|
||||||
|
mEnvironment.getStateManager()->endGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
// update physics
|
||||||
|
osg::Timer_t beforePhysicsTick = osg::Timer::instance()->tick();
|
||||||
|
if (mEnvironment.getStateManager()->getState()!=
|
||||||
|
MWBase::StateManager::State_NoGame)
|
||||||
|
{
|
||||||
|
mEnvironment.getWorld()->updatePhysics(frametime, guiActive);
|
||||||
|
}
|
||||||
|
osg::Timer_t afterPhysicsTick = osg::Timer::instance()->tick();
|
||||||
|
|
||||||
|
// update world
|
||||||
|
osg::Timer_t beforeWorldTick = osg::Timer::instance()->tick();
|
||||||
|
if (mEnvironment.getStateManager()->getState()!=
|
||||||
|
MWBase::StateManager::State_NoGame)
|
||||||
|
{
|
||||||
|
mEnvironment.getWorld()->update(frametime, guiActive);
|
||||||
|
}
|
||||||
|
osg::Timer_t afterWorldTick = osg::Timer::instance()->tick();
|
||||||
|
|
||||||
|
// update GUI
|
||||||
|
mEnvironment.getWindowManager()->onFrame(frametime);
|
||||||
|
|
||||||
|
unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber();
|
||||||
|
osg::Stats* stats = mViewer->getViewerStats();
|
||||||
|
stats->setAttribute(frameNumber, "script_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforeScriptTick));
|
||||||
|
stats->setAttribute(frameNumber, "script_time_taken", osg::Timer::instance()->delta_s(beforeScriptTick, afterScriptTick));
|
||||||
|
stats->setAttribute(frameNumber, "script_time_end", osg::Timer::instance()->delta_s(mStartTick, afterScriptTick));
|
||||||
|
|
||||||
|
stats->setAttribute(frameNumber, "mechanics_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforeMechanicsTick));
|
||||||
|
stats->setAttribute(frameNumber, "mechanics_time_taken", osg::Timer::instance()->delta_s(beforeMechanicsTick, afterMechanicsTick));
|
||||||
|
stats->setAttribute(frameNumber, "mechanics_time_end", osg::Timer::instance()->delta_s(mStartTick, afterMechanicsTick));
|
||||||
|
|
||||||
|
stats->setAttribute(frameNumber, "physics_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforePhysicsTick));
|
||||||
|
stats->setAttribute(frameNumber, "physics_time_taken", osg::Timer::instance()->delta_s(beforePhysicsTick, afterPhysicsTick));
|
||||||
|
stats->setAttribute(frameNumber, "physics_time_end", osg::Timer::instance()->delta_s(mStartTick, afterPhysicsTick));
|
||||||
|
|
||||||
|
stats->setAttribute(frameNumber, "world_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforeWorldTick));
|
||||||
|
stats->setAttribute(frameNumber, "world_time_taken", osg::Timer::instance()->delta_s(beforeWorldTick, afterWorldTick));
|
||||||
|
stats->setAttribute(frameNumber, "world_time_end", osg::Timer::instance()->delta_s(mStartTick, afterWorldTick));
|
||||||
|
|
||||||
|
if (stats->collectStats("resource"))
|
||||||
|
{
|
||||||
|
mResourceSystem->reportStats(frameNumber, stats);
|
||||||
|
|
||||||
|
stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems());
|
||||||
|
stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads());
|
||||||
|
|
||||||
|
mEnvironment.getWorld()->getNavigator()->reportStats(frameNumber, *stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "Error in frame: " << e.what();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
|
||||||
|
: mWindow(nullptr)
|
||||||
|
, mEncoding(ToUTF8::WINDOWS_1252)
|
||||||
|
, mEncoder(nullptr)
|
||||||
|
, mScreenCaptureOperation(nullptr)
|
||||||
|
, mSkipMenu (false)
|
||||||
|
, mUseSound (true)
|
||||||
|
, mCompileAll (false)
|
||||||
|
, mCompileAllDialogue (false)
|
||||||
|
, mWarningsMode (1)
|
||||||
|
, mScriptConsoleMode (false)
|
||||||
|
, mActivationDistanceOverride(-1)
|
||||||
|
, mGrab(true)
|
||||||
|
, mExportFonts(false)
|
||||||
|
, mRandomSeed(0)
|
||||||
|
, mScriptContext (0)
|
||||||
|
, mFSStrict (false)
|
||||||
|
, mScriptBlacklistUse (true)
|
||||||
|
, mNewGame (false)
|
||||||
|
, mCfgMgr(configurationManager)
|
||||||
|
{
|
||||||
|
MWClass::registerClasses();
|
||||||
|
|
||||||
|
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads
|
||||||
|
|
||||||
|
Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE|SDL_INIT_GAMECONTROLLER|SDL_INIT_JOYSTICK|SDL_INIT_SENSOR;
|
||||||
|
if(SDL_WasInit(flags) == 0)
|
||||||
|
{
|
||||||
|
SDL_SetMainReady();
|
||||||
|
if(SDL_Init(flags) != 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Could not initialize SDL! " + std::string(SDL_GetError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mStartTick = osg::Timer::instance()->tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
OMW::Engine::~Engine()
|
||||||
|
{
|
||||||
|
mEnvironment.cleanup();
|
||||||
|
|
||||||
|
delete mScriptContext;
|
||||||
|
mScriptContext = nullptr;
|
||||||
|
|
||||||
|
mWorkQueue = nullptr;
|
||||||
|
|
||||||
|
mViewer = nullptr;
|
||||||
|
|
||||||
|
mResourceSystem.reset();
|
||||||
|
|
||||||
|
delete mEncoder;
|
||||||
|
mEncoder = nullptr;
|
||||||
|
|
||||||
|
if (mWindow)
|
||||||
|
{
|
||||||
|
SDL_DestroyWindow(mWindow);
|
||||||
|
mWindow = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::enableFSStrict(bool fsStrict)
|
||||||
|
{
|
||||||
|
mFSStrict = fsStrict;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set data dir
|
||||||
|
|
||||||
|
void OMW::Engine::setDataDirs (const Files::PathContainer& dataDirs)
|
||||||
|
{
|
||||||
|
mDataDirs = dataDirs;
|
||||||
|
mDataDirs.insert(mDataDirs.begin(), (mResDir / "vfs"));
|
||||||
|
mFileCollections = Files::Collections (mDataDirs, !mFSStrict);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add BSA archive
|
||||||
|
void OMW::Engine::addArchive (const std::string& archive) {
|
||||||
|
mArchives.push_back(archive);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set resource dir
|
||||||
|
void OMW::Engine::setResourceDir (const boost::filesystem::path& parResDir)
|
||||||
|
{
|
||||||
|
mResDir = parResDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set start cell name
|
||||||
|
void OMW::Engine::setCell (const std::string& cellName)
|
||||||
|
{
|
||||||
|
mCellName = cellName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::addContentFile(const std::string& file)
|
||||||
|
{
|
||||||
|
mContentFiles.push_back(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame)
|
||||||
|
{
|
||||||
|
mSkipMenu = skipMenu;
|
||||||
|
mNewGame = newGame;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OMW::Engine::loadSettings (Settings::Manager & settings)
|
||||||
|
{
|
||||||
|
// Create the settings manager and load default settings file
|
||||||
|
const std::string localdefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string();
|
||||||
|
const std::string globaldefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string();
|
||||||
|
|
||||||
|
// prefer local
|
||||||
|
if (boost::filesystem::exists(localdefault))
|
||||||
|
settings.loadDefault(localdefault);
|
||||||
|
else if (boost::filesystem::exists(globaldefault))
|
||||||
|
settings.loadDefault(globaldefault);
|
||||||
|
else
|
||||||
|
throw std::runtime_error ("No default settings file found! Make sure the file \"settings-default.cfg\" was properly installed.");
|
||||||
|
|
||||||
|
// load user settings if they exist
|
||||||
|
const std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string();
|
||||||
|
if (boost::filesystem::exists(settingspath))
|
||||||
|
settings.loadUser(settingspath);
|
||||||
|
|
||||||
|
return settingspath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::createWindow(Settings::Manager& settings)
|
||||||
|
{
|
||||||
|
int screen = settings.getInt("screen", "Video");
|
||||||
|
int width = settings.getInt("resolution x", "Video");
|
||||||
|
int height = settings.getInt("resolution y", "Video");
|
||||||
|
bool fullscreen = settings.getBool("fullscreen", "Video");
|
||||||
|
bool windowBorder = settings.getBool("window border", "Video");
|
||||||
|
bool vsync = settings.getBool("vsync", "Video");
|
||||||
|
int antialiasing = settings.getInt("antialiasing", "Video");
|
||||||
|
|
||||||
|
int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen),
|
||||||
|
pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen);
|
||||||
|
|
||||||
|
if(fullscreen)
|
||||||
|
{
|
||||||
|
pos_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen);
|
||||||
|
pos_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 flags = SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE;
|
||||||
|
if(fullscreen)
|
||||||
|
flags |= SDL_WINDOW_FULLSCREEN;
|
||||||
|
|
||||||
|
if (!windowBorder)
|
||||||
|
flags |= SDL_WINDOW_BORDERLESS;
|
||||||
|
|
||||||
|
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS,
|
||||||
|
settings.getBool("minimize on focus loss", "Video") ? "1" : "0");
|
||||||
|
|
||||||
|
checkSDLError(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8));
|
||||||
|
checkSDLError(SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8));
|
||||||
|
checkSDLError(SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8));
|
||||||
|
checkSDLError(SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0));
|
||||||
|
checkSDLError(SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24));
|
||||||
|
|
||||||
|
if (antialiasing > 0)
|
||||||
|
{
|
||||||
|
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1));
|
||||||
|
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing));
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!mWindow)
|
||||||
|
{
|
||||||
|
mWindow = SDL_CreateWindow("OpenMW", pos_x, pos_y, width, height, flags);
|
||||||
|
if (!mWindow)
|
||||||
|
{
|
||||||
|
// Try with a lower AA
|
||||||
|
if (antialiasing > 0)
|
||||||
|
{
|
||||||
|
Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying " << antialiasing/2;
|
||||||
|
antialiasing /= 2;
|
||||||
|
Settings::Manager::setInt("antialiasing", "Video", antialiasing);
|
||||||
|
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::stringstream error;
|
||||||
|
error << "Failed to create SDL window: " << SDL_GetError();
|
||||||
|
throw std::runtime_error(error.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setWindowIcon();
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
|
||||||
|
SDL_GetWindowPosition(mWindow, &traits->x, &traits->y);
|
||||||
|
SDL_GetWindowSize(mWindow, &traits->width, &traits->height);
|
||||||
|
traits->windowName = SDL_GetWindowTitle(mWindow);
|
||||||
|
traits->windowDecoration = !(SDL_GetWindowFlags(mWindow)&SDL_WINDOW_BORDERLESS);
|
||||||
|
traits->screenNum = SDL_GetWindowDisplayIndex(mWindow);
|
||||||
|
// We tried to get rid of the hardcoding but failed: https://github.com/OpenMW/openmw/pull/1771
|
||||||
|
// Here goes kcat's quote:
|
||||||
|
// It's ultimately a chicken and egg problem, and the reason why the code is like it was in the first place.
|
||||||
|
// It needs a context to get the current attributes, but it needs the attributes to set up the context.
|
||||||
|
// So it just specifies the same values that were given to SDL in the hopes that it's good enough to what the window eventually gets.
|
||||||
|
traits->red = 8;
|
||||||
|
traits->green = 8;
|
||||||
|
traits->blue = 8;
|
||||||
|
traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel
|
||||||
|
traits->depth = 24;
|
||||||
|
traits->stencil = 8;
|
||||||
|
traits->vsync = vsync;
|
||||||
|
traits->doubleBuffer = true;
|
||||||
|
traits->inheritedWindowData = new SDLUtil::GraphicsWindowSDL2::WindowData(mWindow);
|
||||||
|
|
||||||
|
osg::ref_ptr<SDLUtil::GraphicsWindowSDL2> graphicsWindow = new SDLUtil::GraphicsWindowSDL2(traits);
|
||||||
|
if(!graphicsWindow->valid()) throw std::runtime_error("Failed to create GraphicsContext");
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Camera> camera = mViewer->getCamera();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::setWindowIcon()
|
||||||
|
{
|
||||||
|
boost::filesystem::ifstream windowIconStream;
|
||||||
|
std::string windowIcon = (mResDir / "mygui" / "openmw.png").string();
|
||||||
|
windowIconStream.open(windowIcon, std::ios_base::in | std::ios_base::binary);
|
||||||
|
if (windowIconStream.fail())
|
||||||
|
Log(Debug::Error) << "Error: Failed to open " << windowIcon;
|
||||||
|
osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png");
|
||||||
|
if (!reader)
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "Error: Failed to read window icon, no png readerwriter found";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
osgDB::ReaderWriter::ReadResult result = reader->readImage(windowIconStream);
|
||||||
|
if (!result.success())
|
||||||
|
Log(Debug::Error) << "Error: Failed to read " << windowIcon << ": " << result.message() << " code " << result.status();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Image> image = result.getImage();
|
||||||
|
auto surface = SDLUtil::imageToSurface(image, true);
|
||||||
|
SDL_SetWindowIcon(mWindow, surface.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||||
|
{
|
||||||
|
mEnvironment.setStateManager (
|
||||||
|
new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0)));
|
||||||
|
|
||||||
|
createWindow(settings);
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Group> rootNode (new osg::Group);
|
||||||
|
|
||||||
|
mViewer->setSceneData(rootNode);
|
||||||
|
|
||||||
|
mVFS.reset(new VFS::Manager(mFSStrict));
|
||||||
|
|
||||||
|
VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true);
|
||||||
|
|
||||||
|
mResourceSystem.reset(new Resource::ResourceSystem(mVFS.get()));
|
||||||
|
mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply(false); // keep to Off for now to allow better state sharing
|
||||||
|
mResourceSystem->getSceneManager()->setFilterSettings(
|
||||||
|
Settings::Manager::getString("texture mag filter", "General"),
|
||||||
|
Settings::Manager::getString("texture min filter", "General"),
|
||||||
|
Settings::Manager::getString("texture mipmap", "General"),
|
||||||
|
Settings::Manager::getInt("anisotropy", "General")
|
||||||
|
);
|
||||||
|
|
||||||
|
int numThreads = Settings::Manager::getInt("preload num threads", "Cells");
|
||||||
|
if (numThreads <= 0)
|
||||||
|
throw std::runtime_error("Invalid setting: 'preload num threads' must be >0");
|
||||||
|
mWorkQueue = new SceneUtil::WorkQueue(numThreads);
|
||||||
|
|
||||||
|
// Create input and UI first to set up a bootstrapping environment for
|
||||||
|
// showing a loading screen and keeping the window responsive while doing so
|
||||||
|
|
||||||
|
std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input_v3.xml").string();
|
||||||
|
bool keybinderUserExists = boost::filesystem::exists(keybinderUser);
|
||||||
|
if(!keybinderUserExists)
|
||||||
|
{
|
||||||
|
std::string input2 = (mCfgMgr.getUserConfigPath() / "input_v2.xml").string();
|
||||||
|
if(boost::filesystem::exists(input2)) {
|
||||||
|
boost::filesystem::copy_file(input2, keybinderUser);
|
||||||
|
keybinderUserExists = boost::filesystem::exists(keybinderUser);
|
||||||
|
Log(Debug::Info) << "Loading keybindings file: " << keybinderUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log(Debug::Info) << "Loading keybindings file: " << keybinderUser;
|
||||||
|
|
||||||
|
const std::string userdefault = mCfgMgr.getUserConfigPath().string() + "/gamecontrollerdb.txt";
|
||||||
|
const std::string localdefault = mCfgMgr.getLocalPath().string() + "/gamecontrollerdb.txt";
|
||||||
|
const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/gamecontrollerdb.txt";
|
||||||
|
|
||||||
|
std::string userGameControllerdb;
|
||||||
|
if (boost::filesystem::exists(userdefault)){
|
||||||
|
userGameControllerdb = userdefault;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
userGameControllerdb = "";
|
||||||
|
|
||||||
|
std::string gameControllerdb;
|
||||||
|
if (boost::filesystem::exists(localdefault))
|
||||||
|
gameControllerdb = localdefault;
|
||||||
|
else if (boost::filesystem::exists(globaldefault))
|
||||||
|
gameControllerdb = globaldefault;
|
||||||
|
else
|
||||||
|
gameControllerdb = ""; //if it doesn't exist, pass in an empty string
|
||||||
|
<<<<<<< HEAD
|
||||||
|
MWInput::InputManager* input =
|
||||||
|
#ifdef USE_OPENXR
|
||||||
|
new MWVR::OpenXRInputManager(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
|
||||||
|
#else
|
||||||
|
new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
|
||||||
|
#endif
|
||||||
|
mEnvironment.setInputManager (input);
|
||||||
|
=======
|
||||||
|
>>>>>>> 6b44b7f245e12566c26b6bdd92448aeb6dd90a85
|
||||||
|
|
||||||
|
std::string myguiResources = (mResDir / "mygui").string();
|
||||||
|
osg::ref_ptr<osg::Group> guiRoot = new osg::Group;
|
||||||
|
guiRoot->setName("GUI Root");
|
||||||
|
guiRoot->setNodeMask(MWRender::Mask_GUI);
|
||||||
|
rootNode->addChild(guiRoot);
|
||||||
|
MWGui::WindowManager* window = new MWGui::WindowManager(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(),
|
||||||
|
mCfgMgr.getLogPath().string() + std::string("/"), myguiResources,
|
||||||
|
mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts,
|
||||||
|
Version::getOpenmwVersionDescription(mResDir.string()), mCfgMgr.getUserConfigPath().string());
|
||||||
|
mEnvironment.setWindowManager (window);
|
||||||
|
|
||||||
|
MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
|
||||||
|
mEnvironment.setInputManager (input);
|
||||||
|
|
||||||
|
// Create sound system
|
||||||
|
mEnvironment.setSoundManager (new MWSound::SoundManager(mVFS.get(), mUseSound));
|
||||||
|
|
||||||
|
#ifdef USE_OPENXR
|
||||||
|
mXrEnvironment.setGUIManager(new MWVR::VRGUIManager(mViewer));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!mSkipMenu)
|
||||||
|
{
|
||||||
|
const std::string& logo = Fallback::Map::getString("Movies_Company_Logo");
|
||||||
|
if (!logo.empty())
|
||||||
|
window->playVideo(logo, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the world
|
||||||
|
mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(),
|
||||||
|
mFileCollections, mContentFiles, mEncoder, mActivationDistanceOverride, mCellName,
|
||||||
|
mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()));
|
||||||
|
mEnvironment.getWorld()->setupPlayer();
|
||||||
|
|
||||||
|
window->setStore(mEnvironment.getWorld()->getStore());
|
||||||
|
window->initUI();
|
||||||
|
|
||||||
|
//Load translation data
|
||||||
|
mTranslationDataStorage.setEncoder(mEncoder);
|
||||||
|
for (size_t i = 0; i < mContentFiles.size(); i++)
|
||||||
|
mTranslationDataStorage.loadTranslationData(mFileCollections, mContentFiles[i]);
|
||||||
|
|
||||||
|
Compiler::registerExtensions (mExtensions);
|
||||||
|
|
||||||
|
// Create script system
|
||||||
|
mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full);
|
||||||
|
mScriptContext->setExtensions (&mExtensions);
|
||||||
|
|
||||||
|
mEnvironment.setScriptManager (new MWScript::ScriptManager (mEnvironment.getWorld()->getStore(), *mScriptContext, mWarningsMode,
|
||||||
|
mScriptBlacklistUse ? mScriptBlacklist : std::vector<std::string>()));
|
||||||
|
|
||||||
|
// Create game mechanics system
|
||||||
|
MWMechanics::MechanicsManager* mechanics = new MWMechanics::MechanicsManager;
|
||||||
|
mEnvironment.setMechanicsManager (mechanics);
|
||||||
|
|
||||||
|
// Create dialog system
|
||||||
|
mEnvironment.setJournal (new MWDialogue::Journal);
|
||||||
|
mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage));
|
||||||
|
|
||||||
|
// scripts
|
||||||
|
if (mCompileAll)
|
||||||
|
{
|
||||||
|
std::pair<int, int> result = mEnvironment.getScriptManager()->compileAll();
|
||||||
|
if (result.first)
|
||||||
|
Log(Debug::Info)
|
||||||
|
<< "compiled " << result.second << " of " << result.first << " scripts ("
|
||||||
|
<< 100*static_cast<double> (result.second)/result.first
|
||||||
|
<< "%)";
|
||||||
|
}
|
||||||
|
if (mCompileAllDialogue)
|
||||||
|
{
|
||||||
|
std::pair<int, int> result = MWDialogue::ScriptTest::compileAll(&mExtensions, mWarningsMode);
|
||||||
|
if (result.first)
|
||||||
|
Log(Debug::Info)
|
||||||
|
<< "compiled " << result.second << " of " << result.first << " dialogue script/actor combinations a("
|
||||||
|
<< 100*static_cast<double> (result.second)/result.first
|
||||||
|
<< "%)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WriteScreenshotToFileOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat)
|
||||||
|
: mScreenshotPath(screenshotPath)
|
||||||
|
, mScreenshotFormat(screenshotFormat)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void operator()(const osg::Image& image, const unsigned int context_id)
|
||||||
|
{
|
||||||
|
// Count screenshots.
|
||||||
|
int shotCount = 0;
|
||||||
|
|
||||||
|
// Find the first unused filename with a do-while
|
||||||
|
std::ostringstream stream;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Reset the stream
|
||||||
|
stream.str("");
|
||||||
|
stream.clear();
|
||||||
|
|
||||||
|
stream << mScreenshotPath << "/screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << mScreenshotFormat;
|
||||||
|
|
||||||
|
} while (boost::filesystem::exists(stream.str()));
|
||||||
|
|
||||||
|
boost::filesystem::ofstream outStream;
|
||||||
|
outStream.open(boost::filesystem::path(stream.str()), std::ios::binary);
|
||||||
|
|
||||||
|
osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension(mScreenshotFormat);
|
||||||
|
if (!readerwriter)
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "Error: Can't write screenshot, no '" << mScreenshotFormat << "' readerwriter found";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(image, outStream);
|
||||||
|
if (!result.success())
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "Error: Can't write screenshot: " << result.message() << " code " << result.status();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string mScreenshotPath;
|
||||||
|
std::string mScreenshotFormat;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialise and enter main loop.
|
||||||
|
void OMW::Engine::go()
|
||||||
|
{
|
||||||
|
assert (!mContentFiles.empty());
|
||||||
|
|
||||||
|
Log(Debug::Info) << "OSG version: " << osgGetVersion();
|
||||||
|
SDL_version sdlVersion;
|
||||||
|
SDL_GetVersion(&sdlVersion);
|
||||||
|
Log(Debug::Info) << "SDL version: " << (int)sdlVersion.major << "." << (int)sdlVersion.minor << "." << (int)sdlVersion.patch;
|
||||||
|
|
||||||
|
Misc::Rng::init(mRandomSeed);
|
||||||
|
|
||||||
|
// Load settings
|
||||||
|
Settings::Manager settings;
|
||||||
|
std::string settingspath;
|
||||||
|
settingspath = loadSettings (settings);
|
||||||
|
|
||||||
|
// Create encoder
|
||||||
|
mEncoder = new ToUTF8::Utf8Encoder(mEncoding);
|
||||||
|
|
||||||
|
// Setup viewer
|
||||||
|
mViewer = new osgViewer::Viewer;
|
||||||
|
mViewer->setReleaseContextAtEndOfFrameHint(false);
|
||||||
|
|
||||||
|
#if OSG_VERSION_GREATER_OR_EQUAL(3,5,5)
|
||||||
|
// Do not try to outsmart the OS thread scheduler (see bug #4785).
|
||||||
|
mViewer->setUseConfigureAffinity(false);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
mScreenCaptureOperation = new WriteScreenshotToFileOperation(
|
||||||
|
mCfgMgr.getScreenshotPath().string(),
|
||||||
|
Settings::Manager::getString("screenshot format", "General"));
|
||||||
|
|
||||||
|
mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation);
|
||||||
|
|
||||||
|
mViewer->addEventHandler(mScreenCaptureHandler);
|
||||||
|
|
||||||
|
mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video"));
|
||||||
|
|
||||||
|
prepareEngine (settings);
|
||||||
|
|
||||||
|
// Setup profiler
|
||||||
|
osg::ref_ptr<Resource::Profiler> statshandler = new Resource::Profiler;
|
||||||
|
|
||||||
|
statshandler->addUserStatsLine("Script", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
|
||||||
|
"script_time_taken", 1000.0, true, false, "script_time_begin", "script_time_end", 10000);
|
||||||
|
statshandler->addUserStatsLine("Mech", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
|
||||||
|
"mechanics_time_taken", 1000.0, true, false, "mechanics_time_begin", "mechanics_time_end", 10000);
|
||||||
|
statshandler->addUserStatsLine("Phys", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
|
||||||
|
"physics_time_taken", 1000.0, true, false, "physics_time_begin", "physics_time_end", 10000);
|
||||||
|
statshandler->addUserStatsLine("World", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
|
||||||
|
"world_time_taken", 1000.0, true, false, "world_time_begin", "world_time_end", 10000);
|
||||||
|
|
||||||
|
mViewer->addEventHandler(statshandler);
|
||||||
|
|
||||||
|
osg::ref_ptr<Resource::StatsHandler> resourceshandler = new Resource::StatsHandler;
|
||||||
|
mViewer->addEventHandler(resourceshandler);
|
||||||
|
|
||||||
|
#ifdef USE_OPENXR
|
||||||
|
auto* root = mViewer->getSceneData();
|
||||||
|
auto* xrViewer = MWVR::Environment::get().getViewer();
|
||||||
|
xrViewer->addChild(root);
|
||||||
|
mViewer->setSceneData(xrViewer);
|
||||||
|
mXrEnvironment.setGUIManager(new MWVR::VRGUIManager(mViewer));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Start the game
|
||||||
|
if (!mSaveGameFile.empty())
|
||||||
|
{
|
||||||
|
mEnvironment.getStateManager()->loadGame(mSaveGameFile);
|
||||||
|
}
|
||||||
|
else if (!mSkipMenu)
|
||||||
|
{
|
||||||
|
// start in main menu
|
||||||
|
mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu);
|
||||||
|
mEnvironment.getSoundManager()->playTitleMusic();
|
||||||
|
const std::string& logo = Fallback::Map::getString("Movies_Morrowind_Logo");
|
||||||
|
if (!logo.empty())
|
||||||
|
mEnvironment.getWindowManager()->playVideo(logo, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mEnvironment.getStateManager()->newGame (!mNewGame);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mStartupScript.empty() && mEnvironment.getStateManager()->getState() == MWState::StateManager::State_Running)
|
||||||
|
{
|
||||||
|
mEnvironment.getWindowManager()->executeInConsole(mStartupScript);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Start the main rendering loop
|
||||||
|
osg::Timer frameTimer;
|
||||||
|
double simulationTime = 0.0;
|
||||||
|
while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest())
|
||||||
|
{
|
||||||
|
double dt = frameTimer.time_s();
|
||||||
|
frameTimer.setStartTick();
|
||||||
|
dt = std::min(dt, 0.2);
|
||||||
|
|
||||||
|
mViewer->advance(simulationTime);
|
||||||
|
|
||||||
|
if (!frame(dt))
|
||||||
|
{
|
||||||
|
OpenThreads::Thread::microSleep(5000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
mViewer->eventTraversal();
|
||||||
|
|
||||||
|
mEnvironment.getWorld()->updateWindowManager();
|
||||||
|
|
||||||
|
#ifdef USE_OPENXR
|
||||||
|
xrViewer->traversals();
|
||||||
|
#else
|
||||||
|
mViewer->updateTraversal();
|
||||||
|
|
||||||
|
mViewer->renderingTraversals();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool guiActive = mEnvironment.getWindowManager()->isGuiMode();
|
||||||
|
if (!guiActive)
|
||||||
|
simulationTime += dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
mEnvironment.limitFrameRate(frameTimer.time_s());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save user settings
|
||||||
|
settings.saveUser(settingspath);
|
||||||
|
|
||||||
|
Log(Debug::Info) << "Quitting peacefully.";
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::setCompileAll (bool all)
|
||||||
|
{
|
||||||
|
mCompileAll = all;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::setCompileAllDialogue (bool all)
|
||||||
|
{
|
||||||
|
mCompileAllDialogue = all;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::setSoundUsage(bool soundUsage)
|
||||||
|
{
|
||||||
|
mUseSound = soundUsage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::setEncoding(const ToUTF8::FromType& encoding)
|
||||||
|
{
|
||||||
|
mEncoding = encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::setScriptConsoleMode (bool enabled)
|
||||||
|
{
|
||||||
|
mScriptConsoleMode = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::setStartupScript (const std::string& path)
|
||||||
|
{
|
||||||
|
mStartupScript = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::setActivationDistanceOverride (int distance)
|
||||||
|
{
|
||||||
|
mActivationDistanceOverride = distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::setWarningsMode (int mode)
|
||||||
|
{
|
||||||
|
mWarningsMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::setScriptBlacklist (const std::vector<std::string>& list)
|
||||||
|
{
|
||||||
|
mScriptBlacklist = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::setScriptBlacklistUse (bool use)
|
||||||
|
{
|
||||||
|
mScriptBlacklistUse = use;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::enableFontExport(bool exportFonts)
|
||||||
|
{
|
||||||
|
mExportFonts = exportFonts;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::setSaveGameFile(const std::string &savegame)
|
||||||
|
{
|
||||||
|
mSaveGameFile = savegame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OMW::Engine::setRandomSeed(unsigned int seed)
|
||||||
|
{
|
||||||
|
mRandomSeed = seed;
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
#include "openxraction.hpp"
|
||||||
|
#include "openxrdebug.hpp"
|
||||||
|
#include "vrenvironment.hpp"
|
||||||
|
#include "openxrmanagerimpl.hpp"
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
|
||||||
|
OpenXRAction::OpenXRAction(
|
||||||
|
XrAction action,
|
||||||
|
XrActionType actionType,
|
||||||
|
const std::string& actionName,
|
||||||
|
const std::string& localName)
|
||||||
|
: mAction(action)
|
||||||
|
, mType(actionType)
|
||||||
|
, mName(actionName)
|
||||||
|
, mLocalName(localName)
|
||||||
|
{
|
||||||
|
VrDebug::setName(action, "OpenMW XR Action " + actionName);
|
||||||
|
};
|
||||||
|
|
||||||
|
OpenXRAction::~OpenXRAction() {
|
||||||
|
if (mAction)
|
||||||
|
{
|
||||||
|
xrDestroyAction(mAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRAction::getFloat(XrPath subactionPath, float& value)
|
||||||
|
{
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
|
||||||
|
getInfo.action = mAction;
|
||||||
|
getInfo.subactionPath = subactionPath;
|
||||||
|
|
||||||
|
XrActionStateFloat xrValue{ XR_TYPE_ACTION_STATE_FLOAT };
|
||||||
|
CHECK_XRCMD(xrGetActionStateFloat(xr->impl().xrSession(), &getInfo, &xrValue));
|
||||||
|
|
||||||
|
if (xrValue.isActive)
|
||||||
|
value = xrValue.currentState;
|
||||||
|
return xrValue.isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRAction::getBool(XrPath subactionPath, bool& value)
|
||||||
|
{
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
|
||||||
|
getInfo.action = mAction;
|
||||||
|
getInfo.subactionPath = subactionPath;
|
||||||
|
|
||||||
|
XrActionStateBoolean xrValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
|
||||||
|
CHECK_XRCMD(xrGetActionStateBoolean(xr->impl().xrSession(), &getInfo, &xrValue));
|
||||||
|
|
||||||
|
if (xrValue.isActive)
|
||||||
|
value = xrValue.currentState;
|
||||||
|
return xrValue.isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pose action only checks if the pose is active or not
|
||||||
|
bool OpenXRAction::getPoseIsActive(XrPath subactionPath)
|
||||||
|
{
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
|
||||||
|
getInfo.action = mAction;
|
||||||
|
getInfo.subactionPath = subactionPath;
|
||||||
|
|
||||||
|
XrActionStatePose xrValue{ XR_TYPE_ACTION_STATE_POSE };
|
||||||
|
CHECK_XRCMD(xrGetActionStatePose(xr->impl().xrSession(), &getInfo, &xrValue));
|
||||||
|
|
||||||
|
return xrValue.isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRAction::applyHaptics(XrPath subactionPath, float amplitude)
|
||||||
|
{
|
||||||
|
amplitude = std::max(0.f, std::min(1.f, amplitude));
|
||||||
|
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
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(xr->impl().xrSession(), &hapticActionInfo, (XrHapticBaseHeader*)&vibration));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
#ifndef OPENXR_ACTION_HPP
|
||||||
|
#define OPENXR_ACTION_HPP
|
||||||
|
|
||||||
|
#include <openxr/openxr.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
/// \brief C++ wrapper for the XrAction type
|
||||||
|
struct OpenXRAction
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
OpenXRAction(const OpenXRAction&) = default;
|
||||||
|
OpenXRAction& operator=(const OpenXRAction&) = default;
|
||||||
|
public:
|
||||||
|
OpenXRAction(XrAction action, XrActionType actionType, const std::string& actionName, const std::string& localName);
|
||||||
|
~OpenXRAction();
|
||||||
|
|
||||||
|
//! Convenience
|
||||||
|
operator XrAction() { return mAction; }
|
||||||
|
|
||||||
|
bool getFloat(XrPath subactionPath, float& value);
|
||||||
|
bool getBool(XrPath subactionPath, bool& value);
|
||||||
|
bool getPoseIsActive(XrPath subactionPath);
|
||||||
|
bool applyHaptics(XrPath subactionPath, float amplitude);
|
||||||
|
|
||||||
|
XrAction mAction = XR_NULL_HANDLE;
|
||||||
|
XrActionType mType;
|
||||||
|
std::string mName;
|
||||||
|
std::string mLocalName;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,232 @@
|
|||||||
|
#include "openxractionset.hpp"
|
||||||
|
#include "openxrdebug.hpp"
|
||||||
|
|
||||||
|
#include "vrenvironment.hpp"
|
||||||
|
#include "openxrmanager.hpp"
|
||||||
|
#include "openxrmanagerimpl.hpp"
|
||||||
|
#include "openxraction.hpp"
|
||||||
|
|
||||||
|
#include <openxr/openxr.h>
|
||||||
|
|
||||||
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// TODO: should implement actual safe strcpy
|
||||||
|
#ifdef __linux__
|
||||||
|
#define strcpy_s(dst, src) int(strcpy(dst, src) != nullptr)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
|
||||||
|
OpenXRActionSet::OpenXRActionSet(const std::string& actionSetName, std::shared_ptr<AxisAction::Deadzone> deadzone)
|
||||||
|
: mActionSet(nullptr)
|
||||||
|
, mLocalizedName(actionSetName)
|
||||||
|
, mInternalName(Misc::StringUtils::lowerCase(actionSetName))
|
||||||
|
, mDeadzone(deadzone)
|
||||||
|
{
|
||||||
|
mActionSet = createActionSet(actionSetName);
|
||||||
|
// When starting to account for more devices than oculus touch, this section may need some expansion/redesign.
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
OpenXRActionSet::createPoseAction(
|
||||||
|
TrackedLimb limb,
|
||||||
|
const std::string& actionName,
|
||||||
|
const std::string& localName)
|
||||||
|
{
|
||||||
|
mTrackerMap.emplace(limb, new PoseAction(std::move(createXRAction(XR_ACTION_TYPE_POSE_INPUT, actionName, localName))));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
OpenXRActionSet::createHapticsAction(
|
||||||
|
TrackedLimb limb,
|
||||||
|
const std::string& actionName,
|
||||||
|
const std::string& localName)
|
||||||
|
{
|
||||||
|
mHapticsMap.emplace(limb, new HapticsAction(std::move(createXRAction(XR_ACTION_TYPE_VIBRATION_OUTPUT, actionName, localName))));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void
|
||||||
|
OpenXRActionSet::createMWAction<AxisAction>(
|
||||||
|
int openMWAction,
|
||||||
|
const std::string& actionName,
|
||||||
|
const std::string& localName)
|
||||||
|
{
|
||||||
|
auto xrAction = createXRAction(AxisAction::ActionType, mInternalName + "_" + actionName, mLocalizedName + " " + localName);
|
||||||
|
mActionMap.emplace(actionName, new AxisAction(openMWAction, std::move(xrAction), mDeadzone));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename A>
|
||||||
|
void
|
||||||
|
OpenXRActionSet::createMWAction(
|
||||||
|
int openMWAction,
|
||||||
|
const std::string& actionName,
|
||||||
|
const std::string& localName)
|
||||||
|
{
|
||||||
|
auto xrAction = createXRAction(A::ActionType, mInternalName + "_" + actionName, mLocalizedName + " " + localName);
|
||||||
|
mActionMap.emplace(actionName, new A(openMWAction, std::move(xrAction)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
OpenXRActionSet::createMWAction(
|
||||||
|
VrControlType controlType,
|
||||||
|
int openMWAction,
|
||||||
|
const std::string& actionName,
|
||||||
|
const std::string& localName)
|
||||||
|
{
|
||||||
|
switch (controlType)
|
||||||
|
{
|
||||||
|
case VrControlType::Press:
|
||||||
|
return createMWAction<ButtonPressAction>(openMWAction, actionName, localName);
|
||||||
|
case VrControlType::LongPress:
|
||||||
|
return createMWAction<ButtonLongPressAction>(openMWAction, actionName, localName);
|
||||||
|
case VrControlType::Hold:
|
||||||
|
return createMWAction<ButtonHoldAction>(openMWAction, actionName, localName);
|
||||||
|
case VrControlType::Axis:
|
||||||
|
return createMWAction<AxisAction>(openMWAction, actionName, localName);
|
||||||
|
//case VrControlType::Pose:
|
||||||
|
// return createMWAction<PoseAction>(openMWAction, actionName, localName);
|
||||||
|
//case VrControlType::Haptic:
|
||||||
|
// return createMWAction<HapticsAction>(openMWAction, actionName, localName);
|
||||||
|
default:
|
||||||
|
Log(Debug::Warning) << "createMWAction: pose/haptics Not implemented here";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
XrActionSet
|
||||||
|
OpenXRActionSet::createActionSet(const std::string& name)
|
||||||
|
{
|
||||||
|
std::string localized_name = name;
|
||||||
|
std::string internal_name = Misc::StringUtils::lowerCase(name);
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
XrActionSet actionSet = XR_NULL_HANDLE;
|
||||||
|
XrActionSetCreateInfo createInfo{ XR_TYPE_ACTION_SET_CREATE_INFO };
|
||||||
|
strcpy_s(createInfo.actionSetName, internal_name.c_str());
|
||||||
|
strcpy_s(createInfo.localizedActionSetName, localized_name.c_str());
|
||||||
|
createInfo.priority = 0;
|
||||||
|
CHECK_XRCMD(xrCreateActionSet(xr->impl().xrInstance(), &createInfo, &actionSet));
|
||||||
|
VrDebug::setName(actionSet, "OpenMW XR Action Set " + name);
|
||||||
|
return actionSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRActionSet::suggestBindings(std::vector<XrActionSuggestedBinding>& xrSuggestedBindings, const SuggestedBindings& mwSuggestedBindings)
|
||||||
|
{
|
||||||
|
std::vector<XrActionSuggestedBinding> suggestedBindings;
|
||||||
|
if (!mTrackerMap.empty())
|
||||||
|
{
|
||||||
|
suggestedBindings.emplace_back(XrActionSuggestedBinding{ *mTrackerMap[TrackedLimb::LEFT_HAND], getXrPath("/user/hand/left/input/aim/pose") });
|
||||||
|
suggestedBindings.emplace_back(XrActionSuggestedBinding{ *mTrackerMap[TrackedLimb::RIGHT_HAND], getXrPath("/user/hand/right/input/aim/pose") });
|
||||||
|
}
|
||||||
|
if(!mHapticsMap.empty())
|
||||||
|
{
|
||||||
|
suggestedBindings.emplace_back(XrActionSuggestedBinding{ *mHapticsMap[TrackedLimb::LEFT_HAND], getXrPath("/user/hand/left/output/haptic") });
|
||||||
|
suggestedBindings.emplace_back(XrActionSuggestedBinding{ *mHapticsMap[TrackedLimb::RIGHT_HAND], getXrPath("/user/hand/right/output/haptic") });
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto& mwSuggestedBinding : mwSuggestedBindings)
|
||||||
|
{
|
||||||
|
auto xrAction = mActionMap.find(mwSuggestedBinding.action);
|
||||||
|
if (xrAction == mActionMap.end())
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "OpenXRActionSet: Unknown action " << mwSuggestedBinding.action;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
suggestedBindings.push_back({ *xrAction->second, getXrPath(mwSuggestedBinding.path) });
|
||||||
|
}
|
||||||
|
|
||||||
|
xrSuggestedBindings.insert(xrSuggestedBindings.end(), suggestedBindings.begin(), suggestedBindings.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
XrSpace OpenXRActionSet::xrActionSpace(TrackedLimb limb)
|
||||||
|
{
|
||||||
|
return mTrackerMap[limb]->xrSpace();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::unique_ptr<OpenXRAction>
|
||||||
|
OpenXRActionSet::createXRAction(
|
||||||
|
XrActionType actionType,
|
||||||
|
const std::string& actionName,
|
||||||
|
const std::string& localName)
|
||||||
|
{
|
||||||
|
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());
|
||||||
|
|
||||||
|
XrAction action = XR_NULL_HANDLE;
|
||||||
|
CHECK_XRCMD(xrCreateAction(mActionSet, &createInfo, &action));
|
||||||
|
return std::unique_ptr<OpenXRAction>{new OpenXRAction{ action, actionType, actionName, localName }};
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
OpenXRActionSet::updateControls()
|
||||||
|
{
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
if (!xr->impl().appShouldReadInput())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const XrActiveActionSet activeActionSet{ mActionSet, XR_NULL_PATH };
|
||||||
|
XrActionsSyncInfo syncInfo{ XR_TYPE_ACTIONS_SYNC_INFO };
|
||||||
|
syncInfo.countActiveActionSets = 1;
|
||||||
|
syncInfo.activeActionSets = &activeActionSet;
|
||||||
|
CHECK_XRCMD(xrSyncActions(xr->impl().xrSession(), &syncInfo));
|
||||||
|
|
||||||
|
mActionQueue.clear();
|
||||||
|
for (auto& action : mActionMap)
|
||||||
|
action.second->updateAndQueue(mActionQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
XrPath OpenXRActionSet::getXrPath(const std::string& path)
|
||||||
|
{
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
XrPath xrpath = 0;
|
||||||
|
CHECK_XRCMD(xrStringToPath(xr->impl().xrInstance(), path.c_str(), &xrpath));
|
||||||
|
return xrpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Action* OpenXRActionSet::nextAction()
|
||||||
|
{
|
||||||
|
if (mActionQueue.empty())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
const auto* action = mActionQueue.front();
|
||||||
|
mActionQueue.pop_front();
|
||||||
|
return action;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Pose
|
||||||
|
OpenXRActionSet::getLimbPose(
|
||||||
|
int64_t time,
|
||||||
|
TrackedLimb limb)
|
||||||
|
{
|
||||||
|
auto it = mTrackerMap.find(limb);
|
||||||
|
if (it == mTrackerMap.end())
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "OpenXRActionSet: No such tracker: " << limb;
|
||||||
|
return Pose{};
|
||||||
|
}
|
||||||
|
|
||||||
|
it->second->update(time);
|
||||||
|
return it->second->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRActionSet::applyHaptics(TrackedLimb limb, float intensity)
|
||||||
|
{
|
||||||
|
auto it = mHapticsMap.find(limb);
|
||||||
|
if (it == mHapticsMap.end())
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "OpenXRActionSet: No such tracker: " << limb;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
it->second->apply(intensity);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
#ifndef OPENXR_ACTIONSET_HPP
|
||||||
|
#define OPENXR_ACTIONSET_HPP
|
||||||
|
|
||||||
|
#include "vrinput.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
/// \brief Generates and manages an OpenXR ActionSet and associated actions.
|
||||||
|
class OpenXRActionSet
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Actions = MWInput::Actions;
|
||||||
|
|
||||||
|
OpenXRActionSet(const std::string& actionSetName, std::shared_ptr<AxisAction::Deadzone> deadzone);
|
||||||
|
|
||||||
|
//! Update all controls and queue any actions
|
||||||
|
void updateControls();
|
||||||
|
|
||||||
|
//! Get next action from queue (repeat until null is returned)
|
||||||
|
const Action* nextAction();
|
||||||
|
|
||||||
|
//! Get current pose of limb in space.
|
||||||
|
Pose getLimbPose(int64_t time, TrackedLimb limb);
|
||||||
|
|
||||||
|
//! Apply haptics of the given intensity to the given limb
|
||||||
|
void applyHaptics(TrackedLimb limb, float intensity);
|
||||||
|
|
||||||
|
XrActionSet xrActionSet() { return mActionSet; };
|
||||||
|
void suggestBindings(std::vector<XrActionSuggestedBinding>& xrSuggestedBindings, const SuggestedBindings& mwSuggestedBindings);
|
||||||
|
|
||||||
|
XrSpace xrActionSpace(TrackedLimb limb);
|
||||||
|
|
||||||
|
void createMWAction(VrControlType controlType, int openMWAction, const std::string& actionName, const std::string& localName);
|
||||||
|
void createPoseAction(TrackedLimb limb, const std::string& actionName, const std::string& localName);
|
||||||
|
void createHapticsAction(TrackedLimb limb, const std::string& actionName, const std::string& localName);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<typename A>
|
||||||
|
void createMWAction(int openMWAction, const std::string& actionName, const std::string& localName);
|
||||||
|
std::unique_ptr<OpenXRAction> createXRAction(XrActionType actionType, const std::string& actionName, const std::string& localName);
|
||||||
|
XrPath getXrPath(const std::string& path);
|
||||||
|
XrActionSet createActionSet(const std::string& name);
|
||||||
|
|
||||||
|
XrActionSet mActionSet{ nullptr };
|
||||||
|
std::string mLocalizedName{};
|
||||||
|
std::string mInternalName{};
|
||||||
|
std::map<std::string, std::unique_ptr<Action>> mActionMap;
|
||||||
|
std::map<TrackedLimb, std::unique_ptr<PoseAction>> mTrackerMap;
|
||||||
|
std::map<TrackedLimb, std::unique_ptr<HapticsAction>> mHapticsMap;
|
||||||
|
std::deque<const Action*> mActionQueue{};
|
||||||
|
std::shared_ptr<AxisAction::Deadzone> mDeadzone;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,36 @@
|
|||||||
|
#include "openxrdebug.hpp"
|
||||||
|
#include "openxrmanagerimpl.hpp"
|
||||||
|
#include "vrenvironment.hpp"
|
||||||
|
|
||||||
|
// The OpenXR SDK's platform headers assume we've included these windows headers
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <objbase.h>
|
||||||
|
|
||||||
|
#elif __linux__
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <GL/glx.h>
|
||||||
|
#undef None
|
||||||
|
|
||||||
|
#else
|
||||||
|
#error Unsupported platform
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <openxr/openxr_platform_defines.h>
|
||||||
|
#include <openxr/openxr_reflection.h>
|
||||||
|
|
||||||
|
void MWVR::VrDebug::setName(uint64_t handle, XrObjectType type, const std::string& name)
|
||||||
|
{
|
||||||
|
auto& xrManager = Environment::get().getManager()->impl();
|
||||||
|
if (xrManager.xrExtensionIsEnabled(XR_EXT_DEBUG_UTILS_EXTENSION_NAME))
|
||||||
|
{
|
||||||
|
XrDebugUtilsObjectNameInfoEXT nameInfo{ XR_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, nullptr };
|
||||||
|
nameInfo.objectHandle = handle;
|
||||||
|
nameInfo.objectType = type;
|
||||||
|
nameInfo.objectName = name.c_str();
|
||||||
|
|
||||||
|
static PFN_xrSetDebugUtilsObjectNameEXT setDebugUtilsObjectNameEXT
|
||||||
|
= reinterpret_cast<PFN_xrSetDebugUtilsObjectNameEXT>(xrManager.xrGetFunction("xrSetDebugUtilsObjectNameEXT"));
|
||||||
|
CHECK_XRCMD(setDebugUtilsObjectNameEXT(xrManager.xrInstance(), &nameInfo));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
#ifndef OPENXR_DEBUG_HPP
|
||||||
|
#define OPENXR_DEBUG_HPP
|
||||||
|
|
||||||
|
#include <openxr/openxr.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
namespace VrDebug
|
||||||
|
{
|
||||||
|
//! Translates an OpenXR object to the associated XrObjectType enum value
|
||||||
|
template<typename T> XrObjectType getObjectType(T t);
|
||||||
|
|
||||||
|
//! Associates a name with an OpenXR symbol if XR_EXT_debug_utils is enabled
|
||||||
|
template<typename T> void setName(T t, const std::string& name);
|
||||||
|
|
||||||
|
//! Associates a name with an OpenXR symbol if XR_EXT_debug_utils is enabled
|
||||||
|
void setName(uint64_t handle, XrObjectType type, const std::string& name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T> inline void MWVR::VrDebug::setName(T t, const std::string& name)
|
||||||
|
{
|
||||||
|
setName(reinterpret_cast<uint64_t>(t), getObjectType(t), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrInstance>(XrInstance)
|
||||||
|
{
|
||||||
|
return XR_OBJECT_TYPE_INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrSession>(XrSession)
|
||||||
|
{
|
||||||
|
return XR_OBJECT_TYPE_SESSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrSpace>(XrSpace)
|
||||||
|
{
|
||||||
|
return XR_OBJECT_TYPE_SPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrActionSet>(XrActionSet)
|
||||||
|
{
|
||||||
|
return XR_OBJECT_TYPE_ACTION_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrAction>(XrAction)
|
||||||
|
{
|
||||||
|
return XR_OBJECT_TYPE_ACTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrDebugUtilsMessengerEXT>(XrDebugUtilsMessengerEXT)
|
||||||
|
{
|
||||||
|
return XR_OBJECT_TYPE_DEBUG_UTILS_MESSENGER_EXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrSpatialAnchorMSFT>(XrSpatialAnchorMSFT)
|
||||||
|
{
|
||||||
|
return XR_OBJECT_TYPE_SPATIAL_ANCHOR_MSFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrHandTrackerEXT>(XrHandTrackerEXT)
|
||||||
|
{
|
||||||
|
return XR_OBJECT_TYPE_HAND_TRACKER_EXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T> inline XrObjectType MWVR::VrDebug::getObjectType(T t)
|
||||||
|
{
|
||||||
|
return XR_OBJECT_TYPE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,188 @@
|
|||||||
|
#include "openxrinput.hpp"
|
||||||
|
|
||||||
|
#include "vrenvironment.hpp"
|
||||||
|
#include "openxrmanager.hpp"
|
||||||
|
#include "openxrmanagerimpl.hpp"
|
||||||
|
#include "openxraction.hpp"
|
||||||
|
|
||||||
|
#include <openxr/openxr.h>
|
||||||
|
|
||||||
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
|
||||||
|
OpenXRInput::OpenXRInput(std::shared_ptr<AxisAction::Deadzone> deadzone)
|
||||||
|
{
|
||||||
|
mActionSets.emplace(ActionSet::Gameplay, OpenXRActionSet("Gameplay", deadzone));
|
||||||
|
mActionSets.emplace(ActionSet::GUI, OpenXRActionSet("GUI", deadzone));
|
||||||
|
mActionSets.emplace(ActionSet::Tracking, OpenXRActionSet("Tracking", deadzone));
|
||||||
|
mActionSets.emplace(ActionSet::Haptics, OpenXRActionSet("Haptics", deadzone));
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Applicable actions not (yet) included
|
||||||
|
A_QuickKey1,
|
||||||
|
A_QuickKey2,
|
||||||
|
A_QuickKey3,
|
||||||
|
A_QuickKey4,
|
||||||
|
A_QuickKey5,
|
||||||
|
A_QuickKey6,
|
||||||
|
A_QuickKey7,
|
||||||
|
A_QuickKey8,
|
||||||
|
A_QuickKey9,
|
||||||
|
A_QuickKey10,
|
||||||
|
A_QuickKeysMenu,
|
||||||
|
A_QuickLoad,
|
||||||
|
A_CycleSpellLeft,
|
||||||
|
A_CycleSpellRight,
|
||||||
|
A_CycleWeaponLeft,
|
||||||
|
A_CycleWeaponRight,
|
||||||
|
A_Screenshot, // Generate a VR screenshot?
|
||||||
|
A_Console, // Currently awkward due to a lack of virtual keyboard, but should be included when that's in place
|
||||||
|
*/
|
||||||
|
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_GameMenu, "game_menu", "Game Menu");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, A_VrMetaMenu, "meta_menu", "Meta Menu");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::LongPress, A_Recenter, "reposition_menu", "Reposition Menu");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_Inventory, "inventory", "Inventory");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_Activate, "activate", "Activate");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Hold, MWInput::A_Use, "use", "Use");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Hold, MWInput::A_Jump, "jump", "Jump");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_ToggleWeapon, "weapon", "Weapon");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_ToggleSpell, "spell", "Spell");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_CycleSpellLeft, "cycle_spell_left", "Cycle Spell Left");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_CycleSpellRight, "cycle_spell_right", "Cycle Spell Right");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_CycleWeaponLeft, "cycle_weapon_left", "Cycle Weapon Left");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_CycleWeaponRight, "cycle_weapon_right", "Cycle Weapon Right");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Hold, MWInput::A_Sneak, "sneak", "Sneak");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_QuickKeysMenu, "quick_menu", "Quick Menu");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Axis, MWInput::A_LookLeftRight, "look_left_right", "Look Left Right");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Axis, MWInput::A_MoveForwardBackward, "move_forward_backward", "Move Forward Backward");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Axis, MWInput::A_MoveLeftRight, "move_left_right", "Move Left Right");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_Journal, "journal_book", "Journal Book");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_QuickSave, "quick_save", "Quick Save");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_Rest, "rest", "Rest");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Axis, A_ActivateTouch, "activate_touched", "Activate Touch");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_AlwaysRun, "always_run", "Always Run");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_AutoMove, "auto_move", "Auto Move");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_ToggleHUD, "toggle_hud", "Toggle HUD");
|
||||||
|
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_ToggleDebug, "toggle_debug", "Toggle the debug hud");
|
||||||
|
|
||||||
|
getActionSet(ActionSet::GUI).createMWAction(VrControlType::Press, MWInput::A_GameMenu, "game_menu", "Game Menu");
|
||||||
|
getActionSet(ActionSet::GUI).createMWAction(VrControlType::LongPress, A_Recenter, "reposition_menu", "Reposition Menu");
|
||||||
|
getActionSet(ActionSet::GUI).createMWAction(VrControlType::Axis, A_MenuUpDown, "menu_up_down", "Menu Up Down");
|
||||||
|
getActionSet(ActionSet::GUI).createMWAction(VrControlType::Axis, A_MenuLeftRight, "menu_left_right", "Menu Left Right");
|
||||||
|
getActionSet(ActionSet::GUI).createMWAction(VrControlType::Press, A_MenuSelect, "menu_select", "Menu Select");
|
||||||
|
getActionSet(ActionSet::GUI).createMWAction(VrControlType::Press, A_MenuBack, "menu_back", "Menu Back");
|
||||||
|
getActionSet(ActionSet::GUI).createMWAction(VrControlType::Hold, MWInput::A_Use, "use", "Use");
|
||||||
|
|
||||||
|
getActionSet(ActionSet::Tracking).createPoseAction(TrackedLimb::LEFT_HAND, "left_hand_pose", "Left Hand Pose");
|
||||||
|
getActionSet(ActionSet::Tracking).createPoseAction(TrackedLimb::RIGHT_HAND, "right_hand_pose", "Right Hand Pose");
|
||||||
|
|
||||||
|
getActionSet(ActionSet::Haptics).createHapticsAction(TrackedLimb::RIGHT_HAND, "right_hand_haptics", "Right Hand Haptics");
|
||||||
|
getActionSet(ActionSet::Haptics).createHapticsAction(TrackedLimb::LEFT_HAND, "left_hand_haptics", "Left Hand Haptics");
|
||||||
|
|
||||||
|
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
auto* trackingManager = Environment::get().getTrackingManager();
|
||||||
|
|
||||||
|
auto leftHandPath = trackingManager->stringToVRPath("/user/hand/left/input/aim/pose");
|
||||||
|
auto rightHandPath = trackingManager->stringToVRPath("/user/hand/right/input/aim/pose");
|
||||||
|
|
||||||
|
xr->impl().tracker().addTrackingSpace(leftHandPath, getActionSet(ActionSet::Tracking).xrActionSpace(TrackedLimb::LEFT_HAND));
|
||||||
|
xr->impl().tracker().addTrackingSpace(rightHandPath, getActionSet(ActionSet::Tracking).xrActionSpace(TrackedLimb::RIGHT_HAND));
|
||||||
|
};
|
||||||
|
|
||||||
|
OpenXRActionSet& OpenXRInput::getActionSet(ActionSet actionSet)
|
||||||
|
{
|
||||||
|
auto it = mActionSets.find(actionSet);
|
||||||
|
if (it == mActionSets.end())
|
||||||
|
throw std::logic_error("No such action set");
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRInput::suggestBindings(ActionSet actionSet, std::string profilePath, const SuggestedBindings& mwSuggestedBindings)
|
||||||
|
{
|
||||||
|
getActionSet(actionSet).suggestBindings(mSuggestedBindings[profilePath], mwSuggestedBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRInput::attachActionSets()
|
||||||
|
{
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
|
||||||
|
// Suggest bindings before attaching
|
||||||
|
for (auto& profile : mSuggestedBindings)
|
||||||
|
{
|
||||||
|
XrPath profilePath = 0;
|
||||||
|
CHECK_XRCMD(
|
||||||
|
xrStringToPath(xr->impl().xrInstance(), profile.first.c_str(), &profilePath));
|
||||||
|
XrInteractionProfileSuggestedBinding xrProfileSuggestedBindings{ XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING };
|
||||||
|
xrProfileSuggestedBindings.interactionProfile = profilePath;
|
||||||
|
xrProfileSuggestedBindings.suggestedBindings = profile.second.data();
|
||||||
|
xrProfileSuggestedBindings.countSuggestedBindings = (uint32_t)profile.second.size();
|
||||||
|
CHECK_XRCMD(xrSuggestInteractionProfileBindings(xr->impl().xrInstance(), &xrProfileSuggestedBindings));
|
||||||
|
mInteractionProfileNames[profilePath] = profile.first;
|
||||||
|
mInteractionProfilePaths[profile.first] = profilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenXR requires that xrAttachSessionActionSets be called at most once per session.
|
||||||
|
// So collect all action sets
|
||||||
|
std::vector<XrActionSet> actionSets;
|
||||||
|
for (auto& actionSet : mActionSets)
|
||||||
|
actionSets.push_back(actionSet.second.xrActionSet());
|
||||||
|
|
||||||
|
// Attach
|
||||||
|
XrSessionActionSetsAttachInfo attachInfo{ XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO };
|
||||||
|
attachInfo.countActionSets = actionSets.size();
|
||||||
|
attachInfo.actionSets = actionSets.data();
|
||||||
|
CHECK_XRCMD(xrAttachSessionActionSets(xr->impl().xrSession(), &attachInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRInput::notifyInteractionProfileChanged()
|
||||||
|
{
|
||||||
|
auto xr = MWVR::Environment::get().getManager();
|
||||||
|
xr->impl().xrSession();
|
||||||
|
|
||||||
|
// Unfortunately, openxr does not tell us WHICH profile has changed.
|
||||||
|
std::array<std::string, 5> topLevelUserPaths =
|
||||||
|
{
|
||||||
|
"/user/hand/left",
|
||||||
|
"/user/hand/right",
|
||||||
|
"/user/head",
|
||||||
|
"/user/gamepad",
|
||||||
|
"/user/treadmill"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto& userPath : topLevelUserPaths)
|
||||||
|
{
|
||||||
|
auto pathIt = mInteractionProfilePaths.find(userPath);
|
||||||
|
if (pathIt == mInteractionProfilePaths.end())
|
||||||
|
{
|
||||||
|
XrPath xrUserPath = XR_NULL_PATH;
|
||||||
|
CHECK_XRCMD(
|
||||||
|
xrStringToPath(xr->impl().xrInstance(), userPath.c_str(), &xrUserPath));
|
||||||
|
mInteractionProfilePaths[userPath] = xrUserPath;
|
||||||
|
pathIt = mInteractionProfilePaths.find(userPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
XrInteractionProfileState interactionProfileState{
|
||||||
|
XR_TYPE_INTERACTION_PROFILE_STATE
|
||||||
|
};
|
||||||
|
|
||||||
|
xrGetCurrentInteractionProfile(xr->impl().xrSession(), pathIt->second, &interactionProfileState);
|
||||||
|
if (interactionProfileState.interactionProfile)
|
||||||
|
{
|
||||||
|
auto activeProfileIt = mActiveInteractionProfiles.find(pathIt->second);
|
||||||
|
if (activeProfileIt == mActiveInteractionProfiles.end() || interactionProfileState.interactionProfile != activeProfileIt->second)
|
||||||
|
{
|
||||||
|
auto activeProfileNameIt = mInteractionProfileNames.find(interactionProfileState.interactionProfile);
|
||||||
|
Log(Debug::Verbose) << userPath << ": Interaction profile changed to '" << activeProfileNameIt->second << "'";
|
||||||
|
mActiveInteractionProfiles[pathIt->second] = interactionProfileState.interactionProfile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
#ifndef OPENXR_INPUT_HPP
|
||||||
|
#define OPENXR_INPUT_HPP
|
||||||
|
|
||||||
|
#include "vrinput.hpp"
|
||||||
|
#include "openxractionset.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
/// \brief Generates and manages OpenXR Actions and ActionSets by generating openxr bindings from a list of SuggestedBindings structs.
|
||||||
|
class OpenXRInput
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using XrSuggestedBindings = std::vector<XrActionSuggestedBinding>;
|
||||||
|
using XrProfileSuggestedBindings = std::map<std::string, XrSuggestedBindings>;
|
||||||
|
|
||||||
|
//! Default constructor, creates two ActionSets: Gameplay and GUI
|
||||||
|
OpenXRInput(std::shared_ptr<AxisAction::Deadzone> deadzone);
|
||||||
|
|
||||||
|
//! Get the specified actionSet.
|
||||||
|
OpenXRActionSet& getActionSet(ActionSet actionSet);
|
||||||
|
|
||||||
|
//! Suggest bindings for the specific actionSet and profile pair. Call things after calling attachActionSets is an error.
|
||||||
|
void suggestBindings(ActionSet actionSet, std::string profile, const SuggestedBindings& mwSuggestedBindings);
|
||||||
|
|
||||||
|
//! Set bindings and attach actionSets to the session.
|
||||||
|
void attachActionSets();
|
||||||
|
|
||||||
|
//! Notify that active interaction profile has changed
|
||||||
|
void notifyInteractionProfileChanged();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::map<ActionSet, OpenXRActionSet> mActionSets{};
|
||||||
|
std::map<XrPath, std::string> mInteractionProfileNames{};
|
||||||
|
std::map<std::string, XrPath> mInteractionProfilePaths{};
|
||||||
|
std::map<XrPath, XrPath> mActiveInteractionProfiles;
|
||||||
|
XrProfileSuggestedBindings mSuggestedBindings{};
|
||||||
|
bool mAttached = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,155 @@
|
|||||||
|
#include "openxrmanager.hpp"
|
||||||
|
#include "openxrdebug.hpp"
|
||||||
|
#include "vrenvironment.hpp"
|
||||||
|
#include "openxrmanagerimpl.hpp"
|
||||||
|
#include "../mwinput/inputmanagerimp.hpp"
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
OpenXRManager::OpenXRManager()
|
||||||
|
: mPrivate(nullptr)
|
||||||
|
, mMutex()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenXRManager::~OpenXRManager()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
OpenXRManager::realized() const
|
||||||
|
{
|
||||||
|
return !!mPrivate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManager::handleEvents()
|
||||||
|
{
|
||||||
|
if (realized())
|
||||||
|
return impl().handleEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameInfo OpenXRManager::waitFrame()
|
||||||
|
{
|
||||||
|
return impl().waitFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManager::beginFrame()
|
||||||
|
{
|
||||||
|
return impl().beginFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManager::endFrame(FrameInfo frameInfo, const std::array<CompositionLayerProjectionView, 2>* layerStack)
|
||||||
|
{
|
||||||
|
return impl().endFrame(frameInfo, layerStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRManager::appShouldSyncFrameLoop() const
|
||||||
|
{
|
||||||
|
if (realized())
|
||||||
|
return impl().appShouldSyncFrameLoop();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRManager::appShouldRender() const
|
||||||
|
{
|
||||||
|
if (realized())
|
||||||
|
return impl().appShouldRender();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRManager::appShouldReadInput() const
|
||||||
|
{
|
||||||
|
if (realized())
|
||||||
|
return impl().appShouldReadInput();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
OpenXRManager::realize(
|
||||||
|
osg::GraphicsContext* gc)
|
||||||
|
{
|
||||||
|
lock_guard lock(mMutex);
|
||||||
|
if (!realized())
|
||||||
|
{
|
||||||
|
gc->makeCurrent();
|
||||||
|
mPrivate = std::make_shared<OpenXRManagerImpl>(gc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManager::enablePredictions()
|
||||||
|
{
|
||||||
|
return impl().enablePredictions();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManager::disablePredictions()
|
||||||
|
{
|
||||||
|
return impl().disablePredictions();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManager::xrResourceAcquired()
|
||||||
|
{
|
||||||
|
return impl().xrResourceAcquired();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManager::xrResourceReleased()
|
||||||
|
{
|
||||||
|
return impl().xrResourceReleased();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<View, 2> OpenXRManager::getPredictedViews(int64_t predictedDisplayTime, ReferenceSpace space)
|
||||||
|
{
|
||||||
|
return impl().getPredictedViews(predictedDisplayTime, space);
|
||||||
|
}
|
||||||
|
|
||||||
|
MWVR::Pose OpenXRManager::getPredictedHeadPose(int64_t predictedDisplayTime, ReferenceSpace space)
|
||||||
|
{
|
||||||
|
return impl().getPredictedHeadPose(predictedDisplayTime, space);
|
||||||
|
}
|
||||||
|
|
||||||
|
long long OpenXRManager::getLastPredictedDisplayTime()
|
||||||
|
{
|
||||||
|
return impl().getLastPredictedDisplayTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
long long OpenXRManager::getLastPredictedDisplayPeriod()
|
||||||
|
{
|
||||||
|
return impl().getLastPredictedDisplayPeriod();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<SwapchainConfig, 2> OpenXRManager::getRecommendedSwapchainConfig() const
|
||||||
|
{
|
||||||
|
return impl().getRecommendedSwapchainConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRManager::xrExtensionIsEnabled(const char* extensionName) const
|
||||||
|
{
|
||||||
|
return impl().xrExtensionIsEnabled(extensionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t OpenXRManager::selectColorFormat()
|
||||||
|
{
|
||||||
|
return impl().selectColorFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t OpenXRManager::selectDepthFormat()
|
||||||
|
{
|
||||||
|
return impl().selectDepthFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManager::eraseFormat(int64_t format)
|
||||||
|
{
|
||||||
|
return impl().eraseFormat(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
OpenXRManager::CleanupOperation::operator()(
|
||||||
|
osg::GraphicsContext* gc)
|
||||||
|
{
|
||||||
|
// TODO: Use this to make proper cleanup such as cleaning up VRFramebuffers.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
|||||||
|
#ifndef MWVR_OPENRXMANAGER_H
|
||||||
|
#define MWVR_OPENRXMANAGER_H
|
||||||
|
#ifndef USE_OPENXR
|
||||||
|
#error "openxrmanager.hpp included without USE_OPENXR defined"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <array>
|
||||||
|
#include <mutex>
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
#include <osg/Camera>
|
||||||
|
#include <osgViewer/Viewer>
|
||||||
|
#include "vrtypes.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
struct XrSwapchainSubImage;
|
||||||
|
struct XrCompositionLayerBaseHeader;
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
class OpenXRManagerImpl;
|
||||||
|
|
||||||
|
/// \brief Manage the openxr runtime and session
|
||||||
|
class OpenXRManager : public osg::Referenced
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class CleanupOperation : public osg::GraphicsOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CleanupOperation() : osg::GraphicsOperation("OpenXRCleanupOperation", false) {};
|
||||||
|
void operator()(osg::GraphicsContext* gc) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
OpenXRManager();
|
||||||
|
|
||||||
|
~OpenXRManager();
|
||||||
|
|
||||||
|
/// Manager has been initialized.
|
||||||
|
bool realized() const;
|
||||||
|
|
||||||
|
//! Forward call to xrWaitFrame()
|
||||||
|
FrameInfo waitFrame();
|
||||||
|
|
||||||
|
//! Forward call to xrBeginFrame()
|
||||||
|
void beginFrame();
|
||||||
|
|
||||||
|
//! Forward call to xrEndFrame()
|
||||||
|
void endFrame(FrameInfo frameInfo, const std::array<CompositionLayerProjectionView, 2>* layerStack);
|
||||||
|
|
||||||
|
//! Whether the app should call the openxr frame sync functions ( xr*Frame() )
|
||||||
|
bool appShouldSyncFrameLoop() const;
|
||||||
|
|
||||||
|
//! Whether the app should render anything.
|
||||||
|
bool appShouldRender() const;
|
||||||
|
|
||||||
|
//! Whether the session is focused and can read input
|
||||||
|
bool appShouldReadInput() const;
|
||||||
|
|
||||||
|
//! Process all openxr events
|
||||||
|
void handleEvents();
|
||||||
|
|
||||||
|
//! Instantiate implementation
|
||||||
|
void realize(osg::GraphicsContext* gc);
|
||||||
|
|
||||||
|
//! Enable pose predictions. Exist to police that predictions are never made out of turn.
|
||||||
|
void enablePredictions();
|
||||||
|
|
||||||
|
//! Disable pose predictions.
|
||||||
|
void disablePredictions();
|
||||||
|
|
||||||
|
//! Must be called every time an openxr resource is acquired to keep track
|
||||||
|
void xrResourceAcquired();
|
||||||
|
|
||||||
|
//! Must be called every time an openxr resource is released to keep track
|
||||||
|
void xrResourceReleased();
|
||||||
|
|
||||||
|
//! Get poses and fov of both eyes at the predicted time, relative to the given reference space. \note Will throw if predictions are disabled.
|
||||||
|
std::array<View, 2> getPredictedViews(int64_t predictedDisplayTime, ReferenceSpace space);
|
||||||
|
|
||||||
|
//! Get the pose of the player's head at the predicted time, relative to the given reference space. \note Will throw if predictions are disabled.
|
||||||
|
MWVR::Pose getPredictedHeadPose(int64_t predictedDisplayTime, ReferenceSpace space);
|
||||||
|
|
||||||
|
//! Last predicted display time returned from xrWaitFrame();
|
||||||
|
long long getLastPredictedDisplayTime();
|
||||||
|
|
||||||
|
//! Last predicted display period returned from xrWaitFrame();
|
||||||
|
long long getLastPredictedDisplayPeriod();
|
||||||
|
|
||||||
|
//! Configuration hints for instantiating swapchains, queried from openxr.
|
||||||
|
std::array<SwapchainConfig, 2> getRecommendedSwapchainConfig() const;
|
||||||
|
|
||||||
|
//! Check whether a given openxr extension is enabled or not
|
||||||
|
bool xrExtensionIsEnabled(const char* extensionName) const;
|
||||||
|
|
||||||
|
//! Selects a color format from among formats offered by the runtime
|
||||||
|
//! Returns 0 if no format is supported.
|
||||||
|
int64_t selectColorFormat();
|
||||||
|
|
||||||
|
//! Selects a depth format from among formats offered by the runtime
|
||||||
|
//! Returns 0 if no format is supported.
|
||||||
|
int64_t selectDepthFormat();
|
||||||
|
|
||||||
|
//! Erase format from list of format candidates
|
||||||
|
void eraseFormat(int64_t format);
|
||||||
|
|
||||||
|
OpenXRManagerImpl& impl() { return *mPrivate; }
|
||||||
|
const OpenXRManagerImpl& impl() const { return *mPrivate; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<OpenXRManagerImpl> mPrivate;
|
||||||
|
std::mutex mMutex;
|
||||||
|
using lock_guard = std::lock_guard<std::mutex>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,686 @@
|
|||||||
|
#include "openxrmanagerimpl.hpp"
|
||||||
|
#include "openxrdebug.hpp"
|
||||||
|
#include "openxrplatform.hpp"
|
||||||
|
#include "openxrswapchain.hpp"
|
||||||
|
#include "openxrswapchainimpl.hpp"
|
||||||
|
#include "openxrtypeconversions.hpp"
|
||||||
|
#include "vrenvironment.hpp"
|
||||||
|
#include "vrinputmanager.hpp"
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
||||||
|
#include <components/esm/loadrace.hpp>
|
||||||
|
|
||||||
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
#include "../mwworld/player.hpp"
|
||||||
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
|
||||||
|
#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(osg::GraphicsContext* gc)
|
||||||
|
: mPlatform(gc)
|
||||||
|
{
|
||||||
|
mInstance = mPlatform.createXrInstance("openmw_vr");
|
||||||
|
|
||||||
|
LogInstanceInfo();
|
||||||
|
|
||||||
|
setupDebugMessenger();
|
||||||
|
|
||||||
|
setupLayerDepth();
|
||||||
|
|
||||||
|
getSystem();
|
||||||
|
|
||||||
|
enumerateViews();
|
||||||
|
|
||||||
|
// TODO: Blend mode
|
||||||
|
// setupBlendMode();
|
||||||
|
|
||||||
|
mSession = mPlatform.createXrSession(mInstance, mSystemId);
|
||||||
|
|
||||||
|
LogReferenceSpaces();
|
||||||
|
|
||||||
|
createReferenceSpaces();
|
||||||
|
|
||||||
|
initTracker();
|
||||||
|
|
||||||
|
getSystemProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::createReferenceSpaces()
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
|
||||||
|
CHECK_XRCMD(xrCreateReferenceSpace(mSession, &createInfo, &mReferenceSpaceLocal));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::getSystem()
|
||||||
|
{
|
||||||
|
XrSystemGetInfo systemInfo{ XR_TYPE_SYSTEM_GET_INFO };
|
||||||
|
systemInfo.formFactor = mFormFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
|
||||||
|
auto res = CHECK_XRCMD(xrGetSystem(mInstance, &systemInfo, &mSystemId));
|
||||||
|
if (!XR_SUCCEEDED(res))
|
||||||
|
mPlatform.initFailure(res, mInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::getSystemProperties()
|
||||||
|
{// Read and log graphics properties for the swapchain
|
||||||
|
CHECK_XRCMD(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::enumerateViews()
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::setupLayerDepth()
|
||||||
|
{
|
||||||
|
// Layer depth is enabled, cache the invariant values
|
||||||
|
if (xrExtensionIsEnabled(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME))
|
||||||
|
{
|
||||||
|
GLfloat depthRange[2] = { 0.f, 1.f };
|
||||||
|
glGetFloatv(GL_DEPTH_RANGE, depthRange);
|
||||||
|
auto nearClip = Settings::Manager::getFloat("near clip", "Camera");
|
||||||
|
|
||||||
|
for (auto& layer : mLayerDepth)
|
||||||
|
{
|
||||||
|
layer.type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR;
|
||||||
|
layer.next = nullptr;
|
||||||
|
layer.minDepth = depthRange[0];
|
||||||
|
layer.maxDepth = depthRange[1];
|
||||||
|
layer.nearZ = nearClip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string XrResultString(XrResult res)
|
||||||
|
{
|
||||||
|
return to_string(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenXRManagerImpl::~OpenXRManagerImpl()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::setupExtensionsAndLayers()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
static XrBool32 xrDebugCallback(
|
||||||
|
XrDebugUtilsMessageSeverityFlagsEXT messageSeverity,
|
||||||
|
XrDebugUtilsMessageTypeFlagsEXT messageType,
|
||||||
|
const XrDebugUtilsMessengerCallbackDataEXT* callbackData,
|
||||||
|
void* userData)
|
||||||
|
{
|
||||||
|
OpenXRManagerImpl* manager = reinterpret_cast<OpenXRManagerImpl*>(userData);
|
||||||
|
(void)manager;
|
||||||
|
std::string severityStr = "";
|
||||||
|
std::string typeStr = "";
|
||||||
|
|
||||||
|
switch (messageSeverity)
|
||||||
|
{
|
||||||
|
case XR_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
|
||||||
|
severityStr = "Verbose"; break;
|
||||||
|
case XR_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
|
||||||
|
severityStr = "Info"; break;
|
||||||
|
case XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
|
||||||
|
severityStr = "Warning"; break;
|
||||||
|
case XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
|
||||||
|
severityStr = "Error"; break;
|
||||||
|
default:
|
||||||
|
severityStr = "Unknown"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (messageType)
|
||||||
|
{
|
||||||
|
case XR_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT:
|
||||||
|
typeStr = "General"; break;
|
||||||
|
case XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT:
|
||||||
|
typeStr = "Validation"; break;
|
||||||
|
case XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT:
|
||||||
|
typeStr = "Performance"; break;
|
||||||
|
case XR_DEBUG_UTILS_MESSAGE_TYPE_CONFORMANCE_BIT_EXT:
|
||||||
|
typeStr = "Conformance"; break;
|
||||||
|
default:
|
||||||
|
typeStr = "Unknown"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log(Debug::Verbose) << "XrCallback: [" << severityStr << "][" << typeStr << "][ID=" << (callbackData->messageId ? callbackData->messageId : "null") << "]: " << callbackData->message;
|
||||||
|
|
||||||
|
return XR_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::setupDebugMessenger(void)
|
||||||
|
{
|
||||||
|
if (xrExtensionIsEnabled(XR_EXT_DEBUG_UTILS_EXTENSION_NAME))
|
||||||
|
{
|
||||||
|
XrDebugUtilsMessengerCreateInfoEXT createInfo{ XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, nullptr };
|
||||||
|
|
||||||
|
// Debug message severity levels
|
||||||
|
if (Settings::Manager::getBool("XR_EXT_debug_utils message level verbose", "VR Debug"))
|
||||||
|
createInfo.messageSeverities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT;
|
||||||
|
if (Settings::Manager::getBool("XR_EXT_debug_utils message level info", "VR Debug"))
|
||||||
|
createInfo.messageSeverities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT;
|
||||||
|
if (Settings::Manager::getBool("XR_EXT_debug_utils message level warning", "VR Debug"))
|
||||||
|
createInfo.messageSeverities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;
|
||||||
|
if (Settings::Manager::getBool("XR_EXT_debug_utils message level error", "VR Debug"))
|
||||||
|
createInfo.messageSeverities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
|
||||||
|
|
||||||
|
// Debug message types
|
||||||
|
if (Settings::Manager::getBool("XR_EXT_debug_utils message type general", "VR Debug"))
|
||||||
|
createInfo.messageTypes |= XR_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT;
|
||||||
|
if (Settings::Manager::getBool("XR_EXT_debug_utils message type validation", "VR Debug"))
|
||||||
|
createInfo.messageTypes |= XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
|
||||||
|
if (Settings::Manager::getBool("XR_EXT_debug_utils message type performance", "VR Debug"))
|
||||||
|
createInfo.messageTypes |= XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
|
||||||
|
if (Settings::Manager::getBool("XR_EXT_debug_utils message type conformance", "VR Debug"))
|
||||||
|
createInfo.messageTypes |= XR_DEBUG_UTILS_MESSAGE_TYPE_CONFORMANCE_BIT_EXT;
|
||||||
|
|
||||||
|
createInfo.userCallback = &xrDebugCallback;
|
||||||
|
createInfo.userData = this;
|
||||||
|
|
||||||
|
PFN_xrCreateDebugUtilsMessengerEXT createDebugUtilsMessenger = reinterpret_cast<PFN_xrCreateDebugUtilsMessengerEXT>(xrGetFunction("xrCreateDebugUtilsMessengerEXT"));
|
||||||
|
assert(createDebugUtilsMessenger);
|
||||||
|
CHECK_XRCMD(createDebugUtilsMessenger(mInstance, &createInfo, &mDebugMessenger));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
OpenXRManagerImpl::LogInstanceInfo() {
|
||||||
|
|
||||||
|
XrInstanceProperties instanceProperties{ XR_TYPE_INSTANCE_PROPERTIES };
|
||||||
|
CHECK_XRCMD(xrGetInstanceProperties(mInstance, &instanceProperties));
|
||||||
|
Log(Debug::Verbose) << "Instance RuntimeName=" << instanceProperties.runtimeName << " RuntimeVersion=" << instanceProperties.runtimeVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
OpenXRManagerImpl::LogReferenceSpaces() {
|
||||||
|
|
||||||
|
uint32_t spaceCount = 0;
|
||||||
|
CHECK_XRCMD(xrEnumerateReferenceSpaces(mSession, 0, &spaceCount, nullptr));
|
||||||
|
std::vector<XrReferenceSpaceType> spaces(spaceCount);
|
||||||
|
CHECK_XRCMD(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();
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameInfo
|
||||||
|
OpenXRManagerImpl::waitFrame()
|
||||||
|
{
|
||||||
|
XrFrameWaitInfo frameWaitInfo{ XR_TYPE_FRAME_WAIT_INFO };
|
||||||
|
XrFrameState frameState{ XR_TYPE_FRAME_STATE };
|
||||||
|
|
||||||
|
CHECK_XRCMD(xrWaitFrame(mSession, &frameWaitInfo, &frameState));
|
||||||
|
mFrameState = frameState;
|
||||||
|
|
||||||
|
FrameInfo frameInfo;
|
||||||
|
|
||||||
|
frameInfo.runtimePredictedDisplayTime = mFrameState.predictedDisplayTime;
|
||||||
|
frameInfo.runtimePredictedDisplayPeriod = mFrameState.predictedDisplayPeriod;
|
||||||
|
frameInfo.runtimeRequestsRender = !!mFrameState.shouldRender;
|
||||||
|
|
||||||
|
return frameInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
OpenXRManagerImpl::beginFrame()
|
||||||
|
{
|
||||||
|
XrFrameBeginInfo frameBeginInfo{ XR_TYPE_FRAME_BEGIN_INFO };
|
||||||
|
CHECK_XRCMD(xrBeginFrame(mSession, &frameBeginInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
OpenXRManagerImpl::endFrame(FrameInfo frameInfo, const std::array<CompositionLayerProjectionView, 2>* layerStack)
|
||||||
|
{
|
||||||
|
std::array<XrCompositionLayerProjectionView, 2> compositionLayerProjectionViews{};
|
||||||
|
XrCompositionLayerProjection layer{};
|
||||||
|
std::array<XrCompositionLayerDepthInfoKHR, 2> compositionLayerDepth{};
|
||||||
|
XrFrameEndInfo frameEndInfo{ XR_TYPE_FRAME_END_INFO };
|
||||||
|
frameEndInfo.displayTime = frameInfo.runtimePredictedDisplayTime;
|
||||||
|
frameEndInfo.environmentBlendMode = mEnvironmentBlendMode;
|
||||||
|
if (layerStack && frameInfo.runtimeRequestsRender)
|
||||||
|
{
|
||||||
|
compositionLayerProjectionViews[(int)Side::LEFT_SIDE] = toXR((*layerStack)[(int)Side::LEFT_SIDE]);
|
||||||
|
compositionLayerProjectionViews[(int)Side::RIGHT_SIDE] = toXR((*layerStack)[(int)Side::RIGHT_SIDE]);
|
||||||
|
layer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION;
|
||||||
|
layer.space = mReferenceSpaceStage;
|
||||||
|
layer.viewCount = 2;
|
||||||
|
layer.views = compositionLayerProjectionViews.data();
|
||||||
|
auto* xrLayerStack = reinterpret_cast<XrCompositionLayerBaseHeader*>(&layer);
|
||||||
|
|
||||||
|
if (xrExtensionIsEnabled(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME))
|
||||||
|
{
|
||||||
|
auto farClip = Settings::Manager::getFloat("viewing distance", "Camera");
|
||||||
|
// All values not set here are set previously as they are constant
|
||||||
|
compositionLayerDepth = mLayerDepth;
|
||||||
|
compositionLayerDepth[(int)Side::LEFT_SIDE].farZ = farClip;
|
||||||
|
compositionLayerDepth[(int)Side::RIGHT_SIDE].farZ = farClip;
|
||||||
|
compositionLayerDepth[(int)Side::LEFT_SIDE].subImage = toXR((*layerStack)[(int)Side::LEFT_SIDE].subImage, true);
|
||||||
|
compositionLayerDepth[(int)Side::RIGHT_SIDE].subImage = toXR((*layerStack)[(int)Side::RIGHT_SIDE].subImage, true);
|
||||||
|
if (compositionLayerDepth[(int)Side::LEFT_SIDE].subImage.swapchain != XR_NULL_HANDLE
|
||||||
|
&& compositionLayerDepth[(int)Side::RIGHT_SIDE].subImage.swapchain != XR_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
compositionLayerProjectionViews[(int)Side::LEFT_SIDE].next = &compositionLayerDepth[(int)Side::LEFT_SIDE];
|
||||||
|
compositionLayerProjectionViews[(int)Side::RIGHT_SIDE].next = &compositionLayerDepth[(int)Side::RIGHT_SIDE];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frameEndInfo.layerCount = 1;
|
||||||
|
frameEndInfo.layers = &xrLayerStack;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
frameEndInfo.layerCount = 0;
|
||||||
|
frameEndInfo.layers = nullptr;
|
||||||
|
}
|
||||||
|
CHECK_XRCMD(xrEndFrame(mSession, &frameEndInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<View, 2>
|
||||||
|
OpenXRManagerImpl::getPredictedViews(
|
||||||
|
int64_t predictedDisplayTime,
|
||||||
|
ReferenceSpace space)
|
||||||
|
{
|
||||||
|
//if (!mPredictionsEnabled)
|
||||||
|
//{
|
||||||
|
// Log(Debug::Error) << "Prediction out of order";
|
||||||
|
// throw std::logic_error("Prediction out of order");
|
||||||
|
//}
|
||||||
|
std::array<XrView, 2> xrViews{ {{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 = predictedDisplayTime;
|
||||||
|
switch (space)
|
||||||
|
{
|
||||||
|
case ReferenceSpace::STAGE:
|
||||||
|
viewLocateInfo.space = mReferenceSpaceStage;
|
||||||
|
break;
|
||||||
|
case ReferenceSpace::VIEW:
|
||||||
|
viewLocateInfo.space = mReferenceSpaceView;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
CHECK_XRCMD(xrLocateViews(mSession, &viewLocateInfo, &viewState, viewCount, &viewCount, xrViews.data()));
|
||||||
|
|
||||||
|
std::array<View, 2> vrViews{};
|
||||||
|
vrViews[(int)Side::LEFT_SIDE].pose = fromXR(xrViews[(int)Side::LEFT_SIDE].pose);
|
||||||
|
vrViews[(int)Side::RIGHT_SIDE].pose = fromXR(xrViews[(int)Side::RIGHT_SIDE].pose);
|
||||||
|
vrViews[(int)Side::LEFT_SIDE].fov = fromXR(xrViews[(int)Side::LEFT_SIDE].fov);
|
||||||
|
vrViews[(int)Side::RIGHT_SIDE].fov = fromXR(xrViews[(int)Side::RIGHT_SIDE].fov);
|
||||||
|
return vrViews;
|
||||||
|
}
|
||||||
|
|
||||||
|
MWVR::Pose OpenXRManagerImpl::getPredictedHeadPose(
|
||||||
|
int64_t predictedDisplayTime,
|
||||||
|
ReferenceSpace space)
|
||||||
|
{
|
||||||
|
if (!mPredictionsEnabled)
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "Prediction out of order";
|
||||||
|
throw std::logic_error("Prediction out of order");
|
||||||
|
}
|
||||||
|
XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION };
|
||||||
|
XrSpace limbSpace = mReferenceSpaceView;
|
||||||
|
XrSpace referenceSpace = XR_NULL_HANDLE;
|
||||||
|
|
||||||
|
switch (space)
|
||||||
|
{
|
||||||
|
case ReferenceSpace::STAGE:
|
||||||
|
referenceSpace = mReferenceSpaceStage;
|
||||||
|
break;
|
||||||
|
case ReferenceSpace::VIEW:
|
||||||
|
referenceSpace = mReferenceSpaceView;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
CHECK_XRCMD(xrLocateSpace(limbSpace, referenceSpace, predictedDisplayTime, &location));
|
||||||
|
|
||||||
|
if (!location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)
|
||||||
|
{
|
||||||
|
// Quat must have a magnitude of 1 but openxr sets it to 0 when tracking is unavailable.
|
||||||
|
// I want a no-track pose to still be valid
|
||||||
|
location.pose.orientation.w = 1;
|
||||||
|
}
|
||||||
|
return MWVR::Pose{
|
||||||
|
fromXR(location.pose.position),
|
||||||
|
fromXR(location.pose.orientation)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::handleEvents()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
|
|
||||||
|
xrQueueEvents();
|
||||||
|
|
||||||
|
while (auto* event = nextEvent())
|
||||||
|
{
|
||||||
|
if (!processEvent(event))
|
||||||
|
{
|
||||||
|
// Do not consider processing an event optional.
|
||||||
|
// Retry once per frame until every event has been successfully processed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
popEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mXrSessionShouldStop)
|
||||||
|
{
|
||||||
|
if (checkStopCondition())
|
||||||
|
{
|
||||||
|
CHECK_XRCMD(xrEndSession(mSession));
|
||||||
|
mXrSessionShouldStop = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const XrEventDataBaseHeader* OpenXRManagerImpl::nextEvent()
|
||||||
|
{
|
||||||
|
if (mEventQueue.size() > 0)
|
||||||
|
return reinterpret_cast<XrEventDataBaseHeader*> (&mEventQueue.front());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRManagerImpl::processEvent(const XrEventDataBaseHeader* header)
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << "OpenXR: Event received: " << to_string(header->type);
|
||||||
|
switch (header->type)
|
||||||
|
{
|
||||||
|
case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED:
|
||||||
|
{
|
||||||
|
const auto* stateChangeEvent = reinterpret_cast<const XrEventDataSessionStateChanged*>(header);
|
||||||
|
return handleSessionStateChanged(*stateChangeEvent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED:
|
||||||
|
MWVR::Environment::get().getInputManager()->notifyInteractionProfileChanged();
|
||||||
|
break;
|
||||||
|
case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING:
|
||||||
|
case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING:
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << "OpenXR: Event ignored";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
OpenXRManagerImpl::handleSessionStateChanged(
|
||||||
|
const XrEventDataSessionStateChanged& stateChangedEvent)
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << "XrEventDataSessionStateChanged: state " << to_string(mSessionState) << "->" << to_string(stateChangedEvent.state);
|
||||||
|
mSessionState = stateChangedEvent.state;
|
||||||
|
|
||||||
|
// Ref: https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#session-states
|
||||||
|
switch (mSessionState)
|
||||||
|
{
|
||||||
|
case XR_SESSION_STATE_IDLE:
|
||||||
|
{
|
||||||
|
mAppShouldSyncFrameLoop = false;
|
||||||
|
mAppShouldRender = false;
|
||||||
|
mAppShouldReadInput = false;
|
||||||
|
mXrSessionShouldStop = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case XR_SESSION_STATE_READY:
|
||||||
|
{
|
||||||
|
mAppShouldSyncFrameLoop = true;
|
||||||
|
mAppShouldRender = false;
|
||||||
|
mAppShouldReadInput = false;
|
||||||
|
mXrSessionShouldStop = false;
|
||||||
|
|
||||||
|
XrSessionBeginInfo beginInfo{ XR_TYPE_SESSION_BEGIN_INFO };
|
||||||
|
beginInfo.primaryViewConfigurationType = mViewConfigType;
|
||||||
|
CHECK_XRCMD(xrBeginSession(mSession, &beginInfo));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case XR_SESSION_STATE_STOPPING:
|
||||||
|
{
|
||||||
|
mAppShouldSyncFrameLoop = false;
|
||||||
|
mAppShouldRender = false;
|
||||||
|
mAppShouldReadInput = false;
|
||||||
|
mXrSessionShouldStop = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case XR_SESSION_STATE_SYNCHRONIZED:
|
||||||
|
{
|
||||||
|
mAppShouldSyncFrameLoop = true;
|
||||||
|
mAppShouldRender = false;
|
||||||
|
mAppShouldReadInput = false;
|
||||||
|
mXrSessionShouldStop = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case XR_SESSION_STATE_VISIBLE:
|
||||||
|
{
|
||||||
|
mAppShouldSyncFrameLoop = true;
|
||||||
|
mAppShouldRender = true;
|
||||||
|
mAppShouldReadInput = false;
|
||||||
|
mXrSessionShouldStop = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case XR_SESSION_STATE_FOCUSED:
|
||||||
|
{
|
||||||
|
mAppShouldSyncFrameLoop = true;
|
||||||
|
mAppShouldRender = true;
|
||||||
|
mAppShouldReadInput = true;
|
||||||
|
mXrSessionShouldStop = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
Log(Debug::Warning) << "XrEventDataSessionStateChanged: Ignoring new state " << to_string(mSessionState);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRManagerImpl::checkStopCondition()
|
||||||
|
{
|
||||||
|
return mAcquiredResources == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRManagerImpl::xrNextEvent(XrEventDataBuffer& eventBuffer)
|
||||||
|
{
|
||||||
|
XrEventDataBaseHeader* baseHeader = reinterpret_cast<XrEventDataBaseHeader*>(&eventBuffer);
|
||||||
|
*baseHeader = { XR_TYPE_EVENT_DATA_BUFFER };
|
||||||
|
const XrResult result = xrPollEvent(mInstance, &eventBuffer);
|
||||||
|
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 false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::popEvent()
|
||||||
|
{
|
||||||
|
if (mEventQueue.size() > 0)
|
||||||
|
mEventQueue.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
OpenXRManagerImpl::xrQueueEvents()
|
||||||
|
{
|
||||||
|
XrEventDataBuffer eventBuffer;
|
||||||
|
while (xrNextEvent(eventBuffer))
|
||||||
|
{
|
||||||
|
mEventQueue.push(eventBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRManagerImpl::xrExtensionIsEnabled(const char* extensionName) const
|
||||||
|
{
|
||||||
|
return mPlatform.extensionEnabled(extensionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::xrResourceAcquired()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
|
mAcquiredResources++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::xrResourceReleased()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
|
if (mAcquiredResources == 0)
|
||||||
|
throw std::logic_error("Releasing a nonexistent resource");
|
||||||
|
mAcquiredResources--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::xrUpdateNames()
|
||||||
|
{
|
||||||
|
VrDebug::setName(mInstance, "OpenMW XR Instance");
|
||||||
|
VrDebug::setName(mSession, "OpenMW XR Session");
|
||||||
|
VrDebug::setName(mReferenceSpaceStage, "OpenMW XR Reference Space Stage");
|
||||||
|
VrDebug::setName(mReferenceSpaceView, "OpenMW XR Reference Space Stage");
|
||||||
|
}
|
||||||
|
|
||||||
|
PFN_xrVoidFunction OpenXRManagerImpl::xrGetFunction(const std::string& name)
|
||||||
|
{
|
||||||
|
PFN_xrVoidFunction function = nullptr;
|
||||||
|
xrGetInstanceProcAddr(mInstance, name.c_str(), &function);
|
||||||
|
return function;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t OpenXRManagerImpl::selectColorFormat()
|
||||||
|
{
|
||||||
|
// Find supported color swapchain format.
|
||||||
|
return mPlatform.selectColorFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t OpenXRManagerImpl::selectDepthFormat()
|
||||||
|
{
|
||||||
|
// Find supported depth swapchain format.
|
||||||
|
return mPlatform.selectDepthFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::eraseFormat(int64_t format)
|
||||||
|
{
|
||||||
|
mPlatform.eraseFormat(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::initTracker()
|
||||||
|
{
|
||||||
|
auto* trackingManager = Environment::get().getTrackingManager();
|
||||||
|
auto headPath = trackingManager->stringToVRPath("/user/head/input/pose");
|
||||||
|
|
||||||
|
mTracker.reset(new OpenXRTracker("pcstage", mReferenceSpaceStage));
|
||||||
|
mTracker->addTrackingSpace(headPath, mReferenceSpaceView);
|
||||||
|
mTrackerToWorldBinding.reset(new VRTrackingToWorldBinding("pcworld", mTracker.get(), headPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::enablePredictions()
|
||||||
|
{
|
||||||
|
mPredictionsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRManagerImpl::disablePredictions()
|
||||||
|
{
|
||||||
|
mPredictionsEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long long OpenXRManagerImpl::getLastPredictedDisplayTime()
|
||||||
|
{
|
||||||
|
return mFrameState.predictedDisplayTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
long long OpenXRManagerImpl::getLastPredictedDisplayPeriod()
|
||||||
|
{
|
||||||
|
return mFrameState.predictedDisplayPeriod;
|
||||||
|
}
|
||||||
|
std::array<SwapchainConfig, 2> OpenXRManagerImpl::getRecommendedSwapchainConfig() const
|
||||||
|
{
|
||||||
|
std::array<SwapchainConfig, 2> config{};
|
||||||
|
for (uint32_t i = 0; i < 2; i++)
|
||||||
|
config[i] = SwapchainConfig{
|
||||||
|
(int)mConfigViews[i].recommendedImageRectWidth,
|
||||||
|
(int)mConfigViews[i].recommendedImageRectHeight,
|
||||||
|
(int)mConfigViews[i].recommendedSwapchainSampleCount,
|
||||||
|
(int)mConfigViews[i].maxImageRectWidth,
|
||||||
|
(int)mConfigViews[i].maxImageRectHeight,
|
||||||
|
(int)mConfigViews[i].maxSwapchainSampleCount,
|
||||||
|
};
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
XrSpace OpenXRManagerImpl::getReferenceSpace(ReferenceSpace space)
|
||||||
|
{
|
||||||
|
switch (space)
|
||||||
|
{
|
||||||
|
case ReferenceSpace::STAGE:
|
||||||
|
return mReferenceSpaceStage;
|
||||||
|
case ReferenceSpace::VIEW:
|
||||||
|
return mReferenceSpaceView;
|
||||||
|
}
|
||||||
|
return XR_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,115 @@
|
|||||||
|
#ifndef OPENXR_MANAGER_IMPL_HPP
|
||||||
|
#define OPENXR_MANAGER_IMPL_HPP
|
||||||
|
|
||||||
|
#include "openxrmanager.hpp"
|
||||||
|
#include "openxrplatform.hpp"
|
||||||
|
#include "openxrtracker.hpp"
|
||||||
|
#include "../mwinput/inputmanagerimp.hpp"
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
||||||
|
|
||||||
|
#include <openxr/openxr.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
#include <map>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
/// \brief Implementation of OpenXRManager
|
||||||
|
class OpenXRManagerImpl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OpenXRManagerImpl(osg::GraphicsContext* gc);
|
||||||
|
~OpenXRManagerImpl(void);
|
||||||
|
|
||||||
|
FrameInfo waitFrame();
|
||||||
|
void beginFrame();
|
||||||
|
void endFrame(FrameInfo frameInfo, const std::array<CompositionLayerProjectionView, 2>* layerStack);
|
||||||
|
bool appShouldSyncFrameLoop() const { return mAppShouldSyncFrameLoop; }
|
||||||
|
bool appShouldRender() const { return mAppShouldRender; }
|
||||||
|
bool appShouldReadInput() const { return mAppShouldReadInput; }
|
||||||
|
std::array<View, 2> getPredictedViews(int64_t predictedDisplayTime, ReferenceSpace space);
|
||||||
|
MWVR::Pose getPredictedHeadPose(int64_t predictedDisplayTime, ReferenceSpace space);
|
||||||
|
void handleEvents();
|
||||||
|
void enablePredictions();
|
||||||
|
void disablePredictions();
|
||||||
|
long long getLastPredictedDisplayTime();
|
||||||
|
long long getLastPredictedDisplayPeriod();
|
||||||
|
std::array<SwapchainConfig, 2> getRecommendedSwapchainConfig() const;
|
||||||
|
XrSpace getReferenceSpace(ReferenceSpace space);
|
||||||
|
XrSession xrSession() const { return mSession; };
|
||||||
|
XrInstance xrInstance() const { return mInstance; };
|
||||||
|
bool xrExtensionIsEnabled(const char* extensionName) const;
|
||||||
|
void xrResourceAcquired();
|
||||||
|
void xrResourceReleased();
|
||||||
|
void xrUpdateNames();
|
||||||
|
PFN_xrVoidFunction xrGetFunction(const std::string& name);
|
||||||
|
int64_t selectColorFormat();
|
||||||
|
int64_t selectDepthFormat();
|
||||||
|
void eraseFormat(int64_t format);
|
||||||
|
OpenXRPlatform& platform() { return mPlatform; }
|
||||||
|
OpenXRTracker& tracker() { return *mTracker; }
|
||||||
|
void initTracker();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void setupExtensionsAndLayers();
|
||||||
|
void setupDebugMessenger(void);
|
||||||
|
void LogInstanceInfo();
|
||||||
|
void LogReferenceSpaces();
|
||||||
|
bool xrNextEvent(XrEventDataBuffer& eventBuffer);
|
||||||
|
void xrQueueEvents();
|
||||||
|
const XrEventDataBaseHeader* nextEvent();
|
||||||
|
bool processEvent(const XrEventDataBaseHeader* header);
|
||||||
|
void popEvent();
|
||||||
|
bool handleSessionStateChanged(const XrEventDataSessionStateChanged& stateChangedEvent);
|
||||||
|
bool checkStopCondition();
|
||||||
|
void createReferenceSpaces();
|
||||||
|
void getSystem();
|
||||||
|
void getSystemProperties();
|
||||||
|
void enumerateViews();
|
||||||
|
void setupLayerDepth();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool initialized = false;
|
||||||
|
bool mPredictionsEnabled = false;
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
XrSpace mReferenceSpaceLocal = XR_NULL_HANDLE;
|
||||||
|
XrFrameState mFrameState{};
|
||||||
|
XrSessionState mSessionState = XR_SESSION_STATE_UNKNOWN;
|
||||||
|
XrDebugUtilsMessengerEXT mDebugMessenger{ nullptr };
|
||||||
|
OpenXRPlatform mPlatform;
|
||||||
|
|
||||||
|
std::unique_ptr<OpenXRTracker> mTracker{ nullptr };
|
||||||
|
std::unique_ptr<VRTrackingToWorldBinding> mTrackerToWorldBinding{ nullptr };
|
||||||
|
|
||||||
|
bool mXrSessionShouldStop = false;
|
||||||
|
bool mAppShouldSyncFrameLoop = false;
|
||||||
|
bool mAppShouldRender = false;
|
||||||
|
bool mAppShouldReadInput = false;
|
||||||
|
|
||||||
|
uint32_t mAcquiredResources = 0;
|
||||||
|
std::mutex mMutex{};
|
||||||
|
std::queue<XrEventDataBuffer> mEventQueue;
|
||||||
|
|
||||||
|
std::array<XrCompositionLayerDepthInfoKHR, 2> mLayerDepth;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,746 @@
|
|||||||
|
#include "openxrswapchainimage.hpp"
|
||||||
|
#include "openxrmanagerimpl.hpp"
|
||||||
|
#include "openxrplatform.hpp"
|
||||||
|
#include "vrenvironment.hpp"
|
||||||
|
|
||||||
|
// The OpenXR SDK's platform headers assume we've included platform headers
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <objbase.h>
|
||||||
|
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
#include <d3d11_1.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#elif __linux__
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <GL/glx.h>
|
||||||
|
#undef None
|
||||||
|
|
||||||
|
#else
|
||||||
|
#error Unsupported platform
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <openxr/openxr.h>
|
||||||
|
#include <openxr/openxr_platform.h>
|
||||||
|
#include <openxr/openxr_platform_defines.h>
|
||||||
|
#include <openxr/openxr_reflection.h>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <deque>
|
||||||
|
#include <cassert>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
|
||||||
|
XrResult CheckXrResult(XrResult res, const char* originator, const char* sourceLocation) {
|
||||||
|
static bool initialized = false;
|
||||||
|
static bool sLogAllXrCalls = false;
|
||||||
|
static bool sContinueOnErrors = false;
|
||||||
|
if (!initialized)
|
||||||
|
{
|
||||||
|
initialized = true;
|
||||||
|
sLogAllXrCalls = Settings::Manager::getBool("log all openxr calls", "VR Debug");
|
||||||
|
sContinueOnErrors = Settings::Manager::getBool("continue on errors", "VR Debug");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto resultString = XrResultString(res);
|
||||||
|
|
||||||
|
if (XR_FAILED(res)) {
|
||||||
|
std::stringstream ss;
|
||||||
|
#ifdef _WIN32
|
||||||
|
ss << sourceLocation << ": OpenXR[Error: " << resultString << "][Thread: " << std::this_thread::get_id() << "]: " << originator;
|
||||||
|
#elif __linux__
|
||||||
|
ss << sourceLocation << ": OpenXR[Error: " << resultString << "][Thread: " << std::this_thread::get_id() << "]: " << originator;
|
||||||
|
#endif
|
||||||
|
Log(Debug::Error) << ss.str();
|
||||||
|
if (!sContinueOnErrors)
|
||||||
|
throw std::runtime_error(ss.str().c_str());
|
||||||
|
}
|
||||||
|
else if (res != XR_SUCCESS || sLogAllXrCalls)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
Log(Debug::Verbose) << sourceLocation << ": OpenXR[" << resultString << "][" << std::this_thread::get_id() << "]: " << originator;
|
||||||
|
#elif __linux__
|
||||||
|
Log(Debug::Verbose) << sourceLocation << ": OpenXR[" << resultString << "][" << std::this_thread::get_id() << "]: " << originator;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRPlatform::enumerateExtensions(const char* layerName, int logIndent)
|
||||||
|
{
|
||||||
|
uint32_t extensionCount = 0;
|
||||||
|
std::vector<XrExtensionProperties> availableExtensions;
|
||||||
|
CHECK_XRCMD(xrEnumerateInstanceExtensionProperties(layerName, 0, &extensionCount, nullptr));
|
||||||
|
availableExtensions.resize(extensionCount, XrExtensionProperties{ XR_TYPE_EXTENSION_PROPERTIES });
|
||||||
|
CHECK_XRCMD(xrEnumerateInstanceExtensionProperties(layerName, availableExtensions.size(), &extensionCount, availableExtensions.data()));
|
||||||
|
|
||||||
|
std::vector<std::string> extensionNames;
|
||||||
|
const std::string indentStr(logIndent, ' ');
|
||||||
|
for (auto& extension : availableExtensions)
|
||||||
|
{
|
||||||
|
if (layerName)
|
||||||
|
mAvailableLayerExtensions[layerName][extension.extensionName] = extension;
|
||||||
|
else
|
||||||
|
mAvailableExtensions[extension.extensionName] = extension;
|
||||||
|
|
||||||
|
Log(Debug::Verbose) << indentStr << "Name=" << extension.extensionName << " SpecVersion=" << extension.extensionVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OpenXRPlatformPrivate
|
||||||
|
{
|
||||||
|
OpenXRPlatformPrivate(osg::GraphicsContext* gc);
|
||||||
|
~OpenXRPlatformPrivate();
|
||||||
|
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
typedef BOOL(WINAPI* P_wglDXSetResourceShareHandleNV)(void* dxObject, HANDLE shareHandle);
|
||||||
|
typedef HANDLE(WINAPI* P_wglDXOpenDeviceNV)(void* dxDevice);
|
||||||
|
typedef BOOL(WINAPI* P_wglDXCloseDeviceNV)(HANDLE hDevice);
|
||||||
|
typedef HANDLE(WINAPI* P_wglDXRegisterObjectNV)(HANDLE hDevice, void* dxObject,
|
||||||
|
GLuint name, GLenum type, GLenum access);
|
||||||
|
typedef BOOL(WINAPI* P_wglDXUnregisterObjectNV)(HANDLE hDevice, HANDLE hObject);
|
||||||
|
typedef BOOL(WINAPI* P_wglDXObjectAccessNV)(HANDLE hObject, GLenum access);
|
||||||
|
typedef BOOL(WINAPI* P_wglDXLockObjectsNV)(HANDLE hDevice, GLint count, HANDLE* hObjects);
|
||||||
|
typedef BOOL(WINAPI* P_wglDXUnlockObjectsNV)(HANDLE hDevice, GLint count, HANDLE* hObjects);
|
||||||
|
|
||||||
|
void initializeD3D11(XrGraphicsRequirementsD3D11KHR requirements)
|
||||||
|
{
|
||||||
|
mD3D11Dll = LoadLibrary("D3D11.dll");
|
||||||
|
|
||||||
|
if (!mD3D11Dll)
|
||||||
|
throw std::runtime_error("Current OpenXR runtime requires DirectX >= 11.0 but D3D11.dll was not found.");
|
||||||
|
|
||||||
|
pD3D11CreateDevice = reinterpret_cast<PFN_D3D11_CREATE_DEVICE>(GetProcAddress(mD3D11Dll, "D3D11CreateDevice"));
|
||||||
|
|
||||||
|
if (!pD3D11CreateDevice)
|
||||||
|
throw std::runtime_error("Symbol 'D3D11CreateDevice' not found in D3D11.dll");
|
||||||
|
|
||||||
|
// Create the device and device context objects
|
||||||
|
pD3D11CreateDevice(
|
||||||
|
nullptr,
|
||||||
|
D3D_DRIVER_TYPE_HARDWARE,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
D3D11_SDK_VERSION,
|
||||||
|
&mD3D11Device,
|
||||||
|
nullptr,
|
||||||
|
&mD3D11ImmediateContext);
|
||||||
|
|
||||||
|
mD3D11bindings.device = mD3D11Device;
|
||||||
|
|
||||||
|
//typedef HANDLE (WINAPI* P_wglDXOpenDeviceNV)(void* dxDevice);
|
||||||
|
//P_wglDXOpenDeviceNV wglDXOpenDeviceNV = reinterpret_cast<P_wglDXOpenDeviceNV>(wglGetProcAddress("wglDXOpenDeviceNV"));
|
||||||
|
//P_wglDXOpenDeviceNV wglDXOpenDeviceNV = reinterpret_cast<P_wglDXOpenDeviceNV>(wglGetProcAddress("wglDXOpenDeviceNV"));
|
||||||
|
|
||||||
|
#define LOAD_WGL(a) a = reinterpret_cast<decltype(a)>(wglGetProcAddress(#a)); if(!a) throw std::runtime_error("Extension WGL_NV_DX_interop2 required to run OpenMW VR via DirectX missing expected symbol '" #a "'.")
|
||||||
|
LOAD_WGL(wglDXSetResourceShareHandleNV);
|
||||||
|
LOAD_WGL(wglDXOpenDeviceNV);
|
||||||
|
LOAD_WGL(wglDXCloseDeviceNV);
|
||||||
|
LOAD_WGL(wglDXRegisterObjectNV);
|
||||||
|
LOAD_WGL(wglDXUnregisterObjectNV);
|
||||||
|
LOAD_WGL(wglDXObjectAccessNV);
|
||||||
|
LOAD_WGL(wglDXLockObjectsNV);
|
||||||
|
LOAD_WGL(wglDXUnlockObjectsNV);
|
||||||
|
#undef LOAD_WGL
|
||||||
|
|
||||||
|
wglDXDevice = wglDXOpenDeviceNV(mD3D11Device);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mWGL_NV_DX_interop2 = false;
|
||||||
|
XrGraphicsBindingD3D11KHR mD3D11bindings{ XR_TYPE_GRAPHICS_BINDING_D3D11_KHR };
|
||||||
|
ID3D11Device* mD3D11Device = nullptr;
|
||||||
|
ID3D11DeviceContext* mD3D11ImmediateContext = nullptr;
|
||||||
|
HMODULE mD3D11Dll = nullptr;
|
||||||
|
PFN_D3D11_CREATE_DEVICE pD3D11CreateDevice = nullptr;
|
||||||
|
|
||||||
|
P_wglDXSetResourceShareHandleNV wglDXSetResourceShareHandleNV = nullptr;
|
||||||
|
P_wglDXOpenDeviceNV wglDXOpenDeviceNV = nullptr;
|
||||||
|
P_wglDXCloseDeviceNV wglDXCloseDeviceNV = nullptr;
|
||||||
|
P_wglDXRegisterObjectNV wglDXRegisterObjectNV = nullptr;
|
||||||
|
P_wglDXUnregisterObjectNV wglDXUnregisterObjectNV = nullptr;
|
||||||
|
P_wglDXObjectAccessNV wglDXObjectAccessNV = nullptr;
|
||||||
|
P_wglDXLockObjectsNV wglDXLockObjectsNV = nullptr;
|
||||||
|
P_wglDXUnlockObjectsNV wglDXUnlockObjectsNV = nullptr;
|
||||||
|
|
||||||
|
HANDLE wglDXDevice = nullptr;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
OpenXRPlatformPrivate::OpenXRPlatformPrivate(osg::GraphicsContext* gc)
|
||||||
|
{
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
typedef const char* (WINAPI* PFNWGLGETEXTENSIONSSTRINGARBPROC) (HDC hdc);
|
||||||
|
PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = 0;
|
||||||
|
wglGetExtensionsStringARB = reinterpret_cast<PFNWGLGETEXTENSIONSSTRINGARBPROC>(wglGetProcAddress("wglGetExtensionsStringARB"));
|
||||||
|
if (wglGetExtensionsStringARB)
|
||||||
|
{
|
||||||
|
std::string wglExtensions = wglGetExtensionsStringARB(wglGetCurrentDC());
|
||||||
|
Log(Debug::Verbose) << "WGL Extensions: " << wglExtensions;
|
||||||
|
mWGL_NV_DX_interop2 = wglExtensions.find("WGL_NV_DX_interop2") != std::string::npos;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log(Debug::Verbose) << "Unable to query WGL extensions";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenXRPlatformPrivate::~OpenXRPlatformPrivate()
|
||||||
|
{
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
if (wglDXDevice)
|
||||||
|
wglDXCloseDeviceNV(wglDXDevice);
|
||||||
|
if (mD3D11ImmediateContext)
|
||||||
|
mD3D11ImmediateContext->Release();
|
||||||
|
if (mD3D11Device)
|
||||||
|
mD3D11Device->Release();
|
||||||
|
if (mD3D11Dll)
|
||||||
|
FreeLibrary(mD3D11Dll);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenXRPlatform::OpenXRPlatform(osg::GraphicsContext* gc)
|
||||||
|
: mPrivate(new OpenXRPlatformPrivate(gc))
|
||||||
|
{
|
||||||
|
// Enumerate layers and their extensions.
|
||||||
|
uint32_t layerCount;
|
||||||
|
CHECK_XRCMD(xrEnumerateApiLayerProperties(0, &layerCount, nullptr));
|
||||||
|
std::vector<XrApiLayerProperties> layers(layerCount, XrApiLayerProperties{ XR_TYPE_API_LAYER_PROPERTIES });
|
||||||
|
CHECK_XRCMD(xrEnumerateApiLayerProperties((uint32_t)layers.size(), &layerCount, layers.data()));
|
||||||
|
|
||||||
|
Log(Debug::Verbose) << "Available Extensions: ";
|
||||||
|
enumerateExtensions(nullptr, 2);
|
||||||
|
Log(Debug::Verbose) << "Available Layers: ";
|
||||||
|
|
||||||
|
if (layers.size() == 0)
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << " No layers available";
|
||||||
|
}
|
||||||
|
for (const XrApiLayerProperties& layer : layers) {
|
||||||
|
Log(Debug::Verbose) << " Name=" << layer.layerName << " SpecVersion=" << layer.layerVersion;
|
||||||
|
mAvailableLayers[layer.layerName] = layer;
|
||||||
|
enumerateExtensions(layer.layerName, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupExtensions();
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenXRPlatform::~OpenXRPlatform()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !XR_KHR_composition_layer_depth \
|
||||||
|
|| !XR_EXT_hp_mixed_reality_controller \
|
||||||
|
|| !XR_EXT_debug_utils \
|
||||||
|
|| !XR_HTC_vive_cosmos_controller_interaction \
|
||||||
|
|| !XR_HUAWEI_controller_interaction
|
||||||
|
|
||||||
|
#error "OpenXR extensions missing. Please upgrade your copy of the OpenXR SDK to 1.0.13 minimum"
|
||||||
|
#endif
|
||||||
|
void OpenXRPlatform::setupExtensions()
|
||||||
|
{
|
||||||
|
std::vector<const char*> optionalExtensions = {
|
||||||
|
XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME,
|
||||||
|
XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME,
|
||||||
|
XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Settings::Manager::getBool("enable XR_KHR_composition_layer_depth", "VR Debug"))
|
||||||
|
optionalExtensions.emplace_back(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME);
|
||||||
|
|
||||||
|
if (Settings::Manager::getBool("enable XR_EXT_debug_utils", "VR Debug"))
|
||||||
|
optionalExtensions.emplace_back(XR_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||||
|
|
||||||
|
selectGraphicsAPIExtension();
|
||||||
|
|
||||||
|
Log(Debug::Verbose) << "Using extensions:";
|
||||||
|
|
||||||
|
auto* graphicsAPIExtension = graphicsAPIExtensionName();
|
||||||
|
if (!graphicsAPIExtension || !enableExtension(graphicsAPIExtension, true))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("No graphics APIs supported by openmw are supported by the OpenXR runtime.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto optionalExtension : optionalExtensions)
|
||||||
|
enableExtension(optionalExtension, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRPlatform::selectDirectX()
|
||||||
|
{
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
if (mPrivate->mWGL_NV_DX_interop2)
|
||||||
|
{
|
||||||
|
if (mAvailableExtensions.count(XR_KHR_D3D11_ENABLE_EXTENSION_NAME))
|
||||||
|
{
|
||||||
|
mGraphicsAPIExtension = XR_KHR_D3D11_ENABLE_EXTENSION_NAME;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log(Debug::Warning) << "Warning: Failed to select DirectX swapchains: OpenXR runtime does not support essential extension '" << XR_KHR_D3D11_ENABLE_EXTENSION_NAME << "'";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log(Debug::Warning) << "Warning: Failed to select DirectX swapchains: Essential WGL extension 'WGL_NV_DX_interop2' not supported by the graphics driver.";
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRPlatform::selectOpenGL()
|
||||||
|
{
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_OPENGL
|
||||||
|
if (mAvailableExtensions.count(XR_KHR_OPENGL_ENABLE_EXTENSION_NAME))
|
||||||
|
{
|
||||||
|
mGraphicsAPIExtension = XR_KHR_OPENGL_ENABLE_EXTENSION_NAME;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log(Debug::Warning) << "Warning: Failed to select OpenGL swapchains: OpenXR runtime does not support essential extension '" << XR_KHR_OPENGL_ENABLE_EXTENSION_NAME << "'";
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_OPENGL
|
||||||
|
#if !XR_KHR_opengl_enable
|
||||||
|
#error "OpenXR extensions missing. Please upgrade your copy of the OpenXR SDK to 1.0.13 minimum"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
#if !XR_KHR_D3D11_enable
|
||||||
|
#error "OpenXR extensions missing. Please upgrade your copy of the OpenXR SDK to 1.0.13 minimum"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* OpenXRPlatform::graphicsAPIExtensionName()
|
||||||
|
{
|
||||||
|
return mGraphicsAPIExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRPlatform::selectGraphicsAPIExtension()
|
||||||
|
{
|
||||||
|
bool preferDirectX = Settings::Manager::getBool("Prefer DirectX swapchains", "VR");
|
||||||
|
|
||||||
|
if (preferDirectX)
|
||||||
|
if (selectDirectX() || selectOpenGL())
|
||||||
|
return;
|
||||||
|
if (selectOpenGL() || selectDirectX())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log(Debug::Verbose) << "Error: No graphics API supported by OpenMW VR is supported by the OpenXR runtime.";
|
||||||
|
throw std::runtime_error("Error: No graphics API supported by OpenMW VR is supported by the OpenXR runtime.");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRPlatform::supportsExtension(const std::string& extensionName) const
|
||||||
|
{
|
||||||
|
return mAvailableExtensions.count(extensionName) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRPlatform::supportsExtension(const std::string& extensionName, uint32_t minimumVersion) const
|
||||||
|
{
|
||||||
|
auto it = mAvailableExtensions.find(extensionName);
|
||||||
|
return it != mAvailableExtensions.end() && it->second.extensionVersion > minimumVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRPlatform::supportsLayer(const std::string& layerName) const
|
||||||
|
{
|
||||||
|
return mAvailableLayers.count(layerName) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRPlatform::supportsLayer(const std::string& layerName, uint32_t minimumVersion) const
|
||||||
|
{
|
||||||
|
auto it = mAvailableLayers.find(layerName);
|
||||||
|
return it != mAvailableLayers.end() && it->second.layerVersion > minimumVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRPlatform::enableExtension(const std::string& extensionName, bool optional)
|
||||||
|
{
|
||||||
|
auto it = mAvailableExtensions.find(extensionName);
|
||||||
|
if (it != mAvailableExtensions.end())
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << " " << extensionName << ": enabled";
|
||||||
|
mEnabledExtensions.push_back(it->second.extensionName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << " " << extensionName << ": disabled (not supported)";
|
||||||
|
if (!optional)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(std::string("Required OpenXR extension ") + extensionName + " not supported by the runtime");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRPlatform::enableExtension(const std::string& extensionName, bool optional, uint32_t minimumVersion)
|
||||||
|
{
|
||||||
|
auto it = mAvailableExtensions.find(extensionName);
|
||||||
|
if (it != mAvailableExtensions.end() && it->second.extensionVersion > minimumVersion)
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << " " << extensionName << ": enabled";
|
||||||
|
mEnabledExtensions.push_back(it->second.extensionName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << " " << extensionName << ": disabled (not supported)";
|
||||||
|
if (!optional)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(std::string("Required OpenXR extension ") + extensionName + " not supported by the runtime");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool OpenXRPlatform::extensionEnabled(const std::string& extensionName) const
|
||||||
|
{
|
||||||
|
for (auto* extension : mEnabledExtensions)
|
||||||
|
if (extension == extensionName)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
XrInstance OpenXRPlatform::createXrInstance(const std::string& name)
|
||||||
|
{
|
||||||
|
XrInstance instance = XR_NULL_HANDLE;
|
||||||
|
XrInstanceCreateInfo createInfo{ XR_TYPE_INSTANCE_CREATE_INFO };
|
||||||
|
createInfo.next = nullptr;
|
||||||
|
createInfo.enabledExtensionCount = mEnabledExtensions.size();
|
||||||
|
createInfo.enabledExtensionNames = mEnabledExtensions.data();
|
||||||
|
strcpy(createInfo.applicationInfo.applicationName, "openmw_vr");
|
||||||
|
createInfo.applicationInfo.apiVersion = XR_CURRENT_API_VERSION;
|
||||||
|
|
||||||
|
auto res = CHECK_XRCMD(xrCreateInstance(&createInfo, &instance));
|
||||||
|
if (!XR_SUCCEEDED(res))
|
||||||
|
initFailure(res, instance);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
XrSession OpenXRPlatform::createXrSession(XrInstance instance, XrSystemId systemId)
|
||||||
|
{
|
||||||
|
XrSession session = XR_NULL_HANDLE;
|
||||||
|
XrResult res = XR_SUCCESS;
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::string graphicsAPIExtension = graphicsAPIExtensionName();
|
||||||
|
if(graphicsAPIExtension == XR_KHR_OPENGL_ENABLE_EXTENSION_NAME)
|
||||||
|
{
|
||||||
|
// Get system requirements
|
||||||
|
PFN_xrGetOpenGLGraphicsRequirementsKHR p_getRequirements = nullptr;
|
||||||
|
CHECK_XRCMD(xrGetInstanceProcAddr(instance, "xrGetOpenGLGraphicsRequirementsKHR", reinterpret_cast<PFN_xrVoidFunction*>(&p_getRequirements)));
|
||||||
|
XrGraphicsRequirementsOpenGLKHR requirements{ XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR };
|
||||||
|
CHECK_XRCMD(p_getRequirements(instance, systemId, &requirements));
|
||||||
|
|
||||||
|
// TODO: Actually get system version
|
||||||
|
const XrVersion systemApiVersion = XR_MAKE_VERSION(4, 6, 0);
|
||||||
|
if (requirements.minApiVersionSupported > systemApiVersion) {
|
||||||
|
std::cout << "Runtime does not support desired Graphics API and/or version" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Session
|
||||||
|
auto DC = wglGetCurrentDC();
|
||||||
|
auto GLRC = wglGetCurrentContext();
|
||||||
|
|
||||||
|
XrGraphicsBindingOpenGLWin32KHR graphicsBindings;
|
||||||
|
graphicsBindings.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR;
|
||||||
|
graphicsBindings.next = nullptr;
|
||||||
|
graphicsBindings.hDC = DC;
|
||||||
|
graphicsBindings.hGLRC = GLRC;
|
||||||
|
|
||||||
|
if (!graphicsBindings.hDC)
|
||||||
|
Log(Debug::Warning) << "Missing DC";
|
||||||
|
|
||||||
|
XrSessionCreateInfo createInfo{ XR_TYPE_SESSION_CREATE_INFO };
|
||||||
|
createInfo.next = &graphicsBindings;
|
||||||
|
createInfo.systemId = systemId;
|
||||||
|
res = CHECK_XRCMD(xrCreateSession(instance, &createInfo, &session));
|
||||||
|
}
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
else if(graphicsAPIExtension == XR_KHR_D3D11_ENABLE_EXTENSION_NAME)
|
||||||
|
{
|
||||||
|
PFN_xrGetD3D11GraphicsRequirementsKHR p_getRequirements = nullptr;
|
||||||
|
CHECK_XRCMD(xrGetInstanceProcAddr(instance, "xrGetD3D11GraphicsRequirementsKHR", reinterpret_cast<PFN_xrVoidFunction*>(&p_getRequirements)));
|
||||||
|
XrGraphicsRequirementsD3D11KHR requirements{ XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR };
|
||||||
|
CHECK_XRCMD(p_getRequirements(instance, systemId, &requirements));
|
||||||
|
mPrivate->initializeD3D11(requirements);
|
||||||
|
|
||||||
|
XrSessionCreateInfo createInfo{ XR_TYPE_SESSION_CREATE_INFO };
|
||||||
|
createInfo.next = &mPrivate->mD3D11bindings;
|
||||||
|
createInfo.systemId = systemId;
|
||||||
|
res = CHECK_XRCMD(xrCreateSession(instance, &createInfo, &session));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::logic_error("Enum value not implemented");
|
||||||
|
}
|
||||||
|
#elif __linux__
|
||||||
|
{
|
||||||
|
// Get system requirements
|
||||||
|
PFN_xrGetOpenGLGraphicsRequirementsKHR p_getRequirements = nullptr;
|
||||||
|
xrGetInstanceProcAddr(instance, "xrGetOpenGLGraphicsRequirementsKHR", reinterpret_cast<PFN_xrVoidFunction*>(&p_getRequirements));
|
||||||
|
XrGraphicsRequirementsOpenGLKHR requirements{ XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR };
|
||||||
|
CHECK_XRCMD(p_getRequirements(instance, systemId, &requirements));
|
||||||
|
|
||||||
|
// TODO: Actually get system version
|
||||||
|
const XrVersion systemApiVersion = XR_MAKE_VERSION(4, 6, 0);
|
||||||
|
if (requirements.minApiVersionSupported > systemApiVersion) {
|
||||||
|
std::cout << "Runtime does not support desired Graphics API and/or version" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Session
|
||||||
|
Display* xDisplay = XOpenDisplay(NULL);
|
||||||
|
GLXContext glxContext = glXGetCurrentContext();
|
||||||
|
GLXDrawable glxDrawable = glXGetCurrentDrawable();
|
||||||
|
|
||||||
|
// TODO: runtimes don't actually care (yet)
|
||||||
|
GLXFBConfig glxFBConfig = 0;
|
||||||
|
uint32_t visualid = 0;
|
||||||
|
|
||||||
|
XrGraphicsBindingOpenGLXlibKHR graphicsBindings;
|
||||||
|
graphicsBindings.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR;
|
||||||
|
graphicsBindings.next = nullptr;
|
||||||
|
graphicsBindings.xDisplay = xDisplay;
|
||||||
|
graphicsBindings.glxContext = glxContext;
|
||||||
|
graphicsBindings.glxDrawable = glxDrawable;
|
||||||
|
graphicsBindings.glxFBConfig = glxFBConfig;
|
||||||
|
graphicsBindings.visualid = visualid;
|
||||||
|
|
||||||
|
if (!graphicsBindings.glxContext)
|
||||||
|
Log(Debug::Warning) << "Missing glxContext";
|
||||||
|
|
||||||
|
if (!graphicsBindings.glxDrawable)
|
||||||
|
Log(Debug::Warning) << "Missing glxDrawable";
|
||||||
|
|
||||||
|
XrSessionCreateInfo createInfo{ XR_TYPE_SESSION_CREATE_INFO };
|
||||||
|
createInfo.next = &graphicsBindings;
|
||||||
|
createInfo.systemId = systemId;
|
||||||
|
res = CHECK_XRCMD(xrCreateSession(instance, &createInfo, &session));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (!XR_SUCCEEDED(res))
|
||||||
|
initFailure(res, instance);
|
||||||
|
|
||||||
|
uint32_t swapchainFormatCount;
|
||||||
|
CHECK_XRCMD(xrEnumerateSwapchainFormats(session, 0, &swapchainFormatCount, nullptr));
|
||||||
|
mSwapchainFormats.resize(swapchainFormatCount);
|
||||||
|
CHECK_XRCMD(xrEnumerateSwapchainFormats(session, (uint32_t)mSwapchainFormats.size(), &swapchainFormatCount, mSwapchainFormats.data()));
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Available Swapchain formats: (" << swapchainFormatCount << ")" << std::endl;
|
||||||
|
|
||||||
|
for (auto format : mSwapchainFormats)
|
||||||
|
{
|
||||||
|
ss << " Enum=" << std::dec << format << " (0x=" << std::hex << format << ")" << std::dec << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log(Debug::Verbose) << ss.str();
|
||||||
|
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For reference: These are the DXGI formats offered by WMR when using D3D11:
|
||||||
|
Enum=29 //(0x=1d) DXGI_FORMAT_R8G8B8A8_UNORM_SRGB
|
||||||
|
Enum=91 //(0x=5b) DXGI_FORMAT_B8G8R8A8_UNORM_SRGB
|
||||||
|
Enum=28 //(0x=1c) DXGI_FORMAT_R8G8B8A8_UNORM
|
||||||
|
Enum=87 //(0x=57) DXGI_FORMAT_B8G8R8A8_UNORM
|
||||||
|
Enum=40 //(0x=28) DXGI_FORMAT_D32_FLOAT
|
||||||
|
Enum=20 //(0x=14) DXGI_FORMAT_D32_FLOAT_S8X24_UINT
|
||||||
|
Enum=45 //(0x=2d) DXGI_FORMAT_D24_UNORM_S8_UINT
|
||||||
|
Enum=55 //(0x=37) DXGI_FORMAT_D16_UNORM
|
||||||
|
* And these extra formats are offered by SteamVR:
|
||||||
|
0xa , // DXGI_FORMAT_R16G16B16A16_FLOAT
|
||||||
|
0x18 , // DXGI_FORMAT_R10G10B10A2_UNORM
|
||||||
|
*/
|
||||||
|
|
||||||
|
int64_t OpenXRPlatform::selectColorFormat()
|
||||||
|
{
|
||||||
|
std::string graphicsAPIExtension = graphicsAPIExtensionName();
|
||||||
|
if (graphicsAPIExtension == XR_KHR_OPENGL_ENABLE_EXTENSION_NAME)
|
||||||
|
{
|
||||||
|
std::vector<int64_t> requestedColorSwapchainFormats;
|
||||||
|
|
||||||
|
if (Settings::Manager::getBool("Prefer sRGB swapchains", "VR"))
|
||||||
|
{
|
||||||
|
requestedColorSwapchainFormats.push_back(0x8C43); // GL_SRGB8_ALPHA8
|
||||||
|
requestedColorSwapchainFormats.push_back(0x8C41); // GL_SRGB8
|
||||||
|
}
|
||||||
|
|
||||||
|
requestedColorSwapchainFormats.push_back(0x8058); // GL_RGBA8
|
||||||
|
requestedColorSwapchainFormats.push_back(0x8F97); // GL_RGBA8_SNORM
|
||||||
|
requestedColorSwapchainFormats.push_back(0x881A); // GL_RGBA16F
|
||||||
|
requestedColorSwapchainFormats.push_back(0x881B); // GL_RGB16F
|
||||||
|
requestedColorSwapchainFormats.push_back(0x8C3A); // GL_R11F_G11F_B10F
|
||||||
|
|
||||||
|
return selectFormat(requestedColorSwapchainFormats);
|
||||||
|
}
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
else if (graphicsAPIExtension == XR_KHR_D3D11_ENABLE_EXTENSION_NAME)
|
||||||
|
{
|
||||||
|
std::vector<int64_t> requestedColorSwapchainFormats;
|
||||||
|
|
||||||
|
if (Settings::Manager::getBool("Prefer sRGB swapchains", "VR"))
|
||||||
|
{
|
||||||
|
requestedColorSwapchainFormats.push_back(0x1d); // DXGI_FORMAT_R8G8B8A8_UNORM_SRGB
|
||||||
|
requestedColorSwapchainFormats.push_back(0x5b); // DXGI_FORMAT_B8G8R8A8_UNORM_SRGB
|
||||||
|
}
|
||||||
|
|
||||||
|
requestedColorSwapchainFormats.push_back(0x1c); // DXGI_FORMAT_R8G8B8A8_UNORM
|
||||||
|
requestedColorSwapchainFormats.push_back(0x57); // DXGI_FORMAT_B8G8R8A8_UNORM
|
||||||
|
requestedColorSwapchainFormats.push_back(0xa); // DXGI_FORMAT_R16G16B16A16_FLOAT
|
||||||
|
requestedColorSwapchainFormats.push_back(0x18); // DXGI_FORMAT_R10G10B10A2_UNORM
|
||||||
|
|
||||||
|
return selectFormat(requestedColorSwapchainFormats);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::logic_error("Enum value not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t OpenXRPlatform::selectDepthFormat()
|
||||||
|
{
|
||||||
|
std::string graphicsAPIExtension = graphicsAPIExtensionName();
|
||||||
|
if (graphicsAPIExtension == XR_KHR_OPENGL_ENABLE_EXTENSION_NAME)
|
||||||
|
{
|
||||||
|
// Find supported depth swapchain format.
|
||||||
|
std::vector<int64_t> requestedDepthSwapchainFormats = {
|
||||||
|
0x88F0, // GL_DEPTH24_STENCIL8
|
||||||
|
0x8CAC, // GL_DEPTH_COMPONENT32F
|
||||||
|
0x81A7, // GL_DEPTH_COMPONENT32
|
||||||
|
0x8DAB, // GL_DEPTH_COMPONENT32F_NV
|
||||||
|
0x8CAD, // GL_DEPTH32_STENCIL8
|
||||||
|
0x81A6, // GL_DEPTH_COMPONENT24
|
||||||
|
// Need 32bit minimum: // 0x81A5, // GL_DEPTH_COMPONENT16
|
||||||
|
};
|
||||||
|
|
||||||
|
return selectFormat(requestedDepthSwapchainFormats);
|
||||||
|
}
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
else if (graphicsAPIExtension == XR_KHR_D3D11_ENABLE_EXTENSION_NAME)
|
||||||
|
{
|
||||||
|
// Find supported color swapchain format.
|
||||||
|
std::vector<int64_t> requestedDepthSwapchainFormats = {
|
||||||
|
0x2d, // DXGI_FORMAT_D24_UNORM_S8_UINT
|
||||||
|
0x14, // DXGI_FORMAT_D32_FLOAT_S8X24_UINT
|
||||||
|
0x28, // DXGI_FORMAT_D32_FLOAT
|
||||||
|
// Need 32bit minimum: 0x37, // DXGI_FORMAT_D16_UNORM
|
||||||
|
};
|
||||||
|
return selectFormat(requestedDepthSwapchainFormats);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::logic_error("Enum value not implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRPlatform::eraseFormat(int64_t format)
|
||||||
|
{
|
||||||
|
for (auto it = mSwapchainFormats.begin(); it != mSwapchainFormats.end(); it++)
|
||||||
|
{
|
||||||
|
if (*it == format)
|
||||||
|
{
|
||||||
|
mSwapchainFormats.erase(it);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t OpenXRPlatform::selectFormat(const std::vector<int64_t>& requestedFormats)
|
||||||
|
{
|
||||||
|
auto it =
|
||||||
|
std::find_first_of(std::begin(requestedFormats), std::end(requestedFormats),
|
||||||
|
mSwapchainFormats.begin(), mSwapchainFormats.end());
|
||||||
|
if (it == std::end(requestedFormats))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return *it;
|
||||||
|
}
|
||||||
|
void* OpenXRPlatform::DXRegisterObject(void* dxResource, uint32_t glName, uint32_t glType, bool discard, void* ntShareHandle)
|
||||||
|
{
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
if (ntShareHandle)
|
||||||
|
{
|
||||||
|
mPrivate->wglDXSetResourceShareHandleNV(dxResource, ntShareHandle);
|
||||||
|
}
|
||||||
|
return mPrivate->wglDXRegisterObjectNV(mPrivate->wglDXDevice, dxResource, glName, glType, 1);
|
||||||
|
#else
|
||||||
|
return nullptr;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
void OpenXRPlatform::DXUnregisterObject(void* dxResourceShareHandle)
|
||||||
|
{
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
mPrivate->wglDXUnregisterObjectNV(mPrivate->wglDXDevice, dxResourceShareHandle);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
void OpenXRPlatform::DXLockObject(void* dxResourceShareHandle)
|
||||||
|
{
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
mPrivate->wglDXLockObjectsNV(mPrivate->wglDXDevice, 1, &dxResourceShareHandle);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
void OpenXRPlatform::DXUnlockObject(void* dxResourceShareHandle)
|
||||||
|
{
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
mPrivate->wglDXUnlockObjectsNV(mPrivate->wglDXDevice, 1, &dxResourceShareHandle);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static XrInstanceProperties
|
||||||
|
getInstanceProperties(XrInstance instance)
|
||||||
|
{
|
||||||
|
XrInstanceProperties properties{ XR_TYPE_INSTANCE_PROPERTIES };
|
||||||
|
if (instance)
|
||||||
|
xrGetInstanceProperties(instance, &properties);
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OpenXRPlatform::getInstanceName(XrInstance instance)
|
||||||
|
{
|
||||||
|
if (instance)
|
||||||
|
return getInstanceProperties(instance).runtimeName;
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
XrVersion OpenXRPlatform::getInstanceVersion(XrInstance instance)
|
||||||
|
{
|
||||||
|
if (instance)
|
||||||
|
return getInstanceProperties(instance).runtimeVersion;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
void OpenXRPlatform::initFailure(XrResult res, XrInstance instance)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
std::string runtimeName = getInstanceName(instance);
|
||||||
|
XrVersion runtimeVersion = getInstanceVersion(instance);
|
||||||
|
ss << "Error caught while initializing VR device: " << XrResultString(res) << std::endl;
|
||||||
|
ss << "Device: " << runtimeName << std::endl;
|
||||||
|
ss << "Version: " << runtimeVersion << std::endl;
|
||||||
|
if (res == XR_ERROR_FORM_FACTOR_UNAVAILABLE)
|
||||||
|
{
|
||||||
|
ss << "Cause: Unable to open VR device. Make sure your device is plugged in and the VR driver is running." << std::endl;
|
||||||
|
ss << std::endl;
|
||||||
|
if (runtimeName == "Oculus" || runtimeName == "Quest")
|
||||||
|
{
|
||||||
|
ss << "Your device has been identified as an Oculus device." << std::endl;
|
||||||
|
ss << "The most common cause for this error when using an oculus device, is quest users attempting to run the game via Virtual Desktop." << std::endl;
|
||||||
|
ss << "Unfortunately this is currently broken, and quest users will need to play via a link cable." << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (res == XR_ERROR_LIMIT_REACHED)
|
||||||
|
{
|
||||||
|
ss << "Cause: Device resources exhausted. Close other VR applications if you have any open. If you have none, you may need to reboot to reset the driver." << std::endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ss << "Cause: Unknown. Make sure your device is plugged in and ready." << std::endl;
|
||||||
|
}
|
||||||
|
throw std::runtime_error(ss.str());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
#ifndef OPENXR_PLATFORM_HPP
|
||||||
|
#define OPENXR_PLATFORM_HPP
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <openxr/openxr.h>
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
// Error management macros and functions. Should be used on every openxr call.
|
||||||
|
#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);
|
||||||
|
std::string XrResultString(XrResult res);
|
||||||
|
|
||||||
|
struct OpenXRPlatformPrivate;
|
||||||
|
|
||||||
|
class OpenXRPlatform
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using ExtensionMap = std::map<std::string, XrExtensionProperties>;
|
||||||
|
using LayerMap = std::map<std::string, XrApiLayerProperties>;
|
||||||
|
using LayerExtensionMap = std::map<std::string, ExtensionMap>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
OpenXRPlatform(osg::GraphicsContext* gc);
|
||||||
|
~OpenXRPlatform();
|
||||||
|
|
||||||
|
const char* graphicsAPIExtensionName();
|
||||||
|
bool supportsExtension(const std::string& extensionName) const;
|
||||||
|
bool supportsExtension(const std::string& extensionName, uint32_t minimumVersion) const;
|
||||||
|
bool supportsLayer(const std::string& layerName) const;
|
||||||
|
bool supportsLayer(const std::string& layerName, uint32_t minimumVersion) const;
|
||||||
|
|
||||||
|
bool enableExtension(const std::string& extensionName, bool optional);
|
||||||
|
bool enableExtension(const std::string& extensionName, bool optional, uint32_t minimumVersion);
|
||||||
|
|
||||||
|
bool extensionEnabled(const std::string& extensionName) const;
|
||||||
|
|
||||||
|
XrInstance createXrInstance(const std::string& name);
|
||||||
|
XrSession createXrSession(XrInstance instance, XrSystemId systemId);
|
||||||
|
|
||||||
|
int64_t selectColorFormat();
|
||||||
|
int64_t selectDepthFormat();
|
||||||
|
int64_t selectFormat(const std::vector<int64_t>& requestedFormats);
|
||||||
|
void eraseFormat(int64_t format);
|
||||||
|
std::vector<int64_t> mSwapchainFormats{};
|
||||||
|
|
||||||
|
/// Registers an object for sharing as if calling wglDXRegisterObjectNV requesting write access.
|
||||||
|
/// If ntShareHandle is not null, wglDXSetResourceShareHandleNV is called first to register the share handle
|
||||||
|
void* DXRegisterObject(void* dxResource, uint32_t glName, uint32_t glType, bool discard, void* ntShareHandle);
|
||||||
|
/// Unregisters an object from sharing as if calling wglDXUnregisterObjectNV
|
||||||
|
void DXUnregisterObject(void* dxResourceShareHandle);
|
||||||
|
/// Locks a DX object for use by OpenGL as if calling wglDXLockObjectsNV
|
||||||
|
void DXLockObject(void* dxResourceShareHandle);
|
||||||
|
/// Unlocks a DX object for use by DirectX as if calling wglDXUnlockObjectsNV
|
||||||
|
void DXUnlockObject(void* dxResourceShareHandle);
|
||||||
|
|
||||||
|
std::string getInstanceName(XrInstance instance);
|
||||||
|
XrVersion getInstanceVersion(XrInstance instance);
|
||||||
|
void initFailure(XrResult, XrInstance);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void enumerateExtensions(const char* layerName, int logIndent);
|
||||||
|
void setupExtensions();
|
||||||
|
void selectGraphicsAPIExtension();
|
||||||
|
bool selectDirectX();
|
||||||
|
bool selectOpenGL();
|
||||||
|
|
||||||
|
ExtensionMap mAvailableExtensions;
|
||||||
|
LayerMap mAvailableLayers;
|
||||||
|
LayerExtensionMap mAvailableLayerExtensions;
|
||||||
|
std::vector<const char*> mEnabledExtensions;
|
||||||
|
const char* mGraphicsAPIExtension = nullptr;
|
||||||
|
|
||||||
|
std::unique_ptr< OpenXRPlatformPrivate > mPrivate;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,48 @@
|
|||||||
|
#include "openxrswapchain.hpp"
|
||||||
|
#include "openxrswapchainimpl.hpp"
|
||||||
|
#include "openxrmanager.hpp"
|
||||||
|
#include "openxrmanagerimpl.hpp"
|
||||||
|
#include "vrenvironment.hpp"
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
|
namespace MWVR {
|
||||||
|
OpenXRSwapchain::OpenXRSwapchain(osg::ref_ptr<osg::State> state, SwapchainConfig config)
|
||||||
|
: mPrivate(new OpenXRSwapchainImpl(state, config))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenXRSwapchain::~OpenXRSwapchain()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRSwapchain::beginFrame(osg::GraphicsContext* gc)
|
||||||
|
{
|
||||||
|
return impl().beginFrame(gc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRSwapchain::endFrame(osg::GraphicsContext* gc, VRFramebuffer& readBuffer)
|
||||||
|
{
|
||||||
|
return impl().endFrame(gc, readBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
int OpenXRSwapchain::width() const
|
||||||
|
{
|
||||||
|
return impl().width();
|
||||||
|
}
|
||||||
|
|
||||||
|
int OpenXRSwapchain::height() const
|
||||||
|
{
|
||||||
|
return impl().height();
|
||||||
|
}
|
||||||
|
|
||||||
|
int OpenXRSwapchain::samples() const
|
||||||
|
{
|
||||||
|
return impl().samples();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRSwapchain::isAcquired() const
|
||||||
|
{
|
||||||
|
return impl().isAcquired();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
#ifndef OPENXR_SWAPCHAIN_HPP
|
||||||
|
#define OPENXR_SWAPCHAIN_HPP
|
||||||
|
|
||||||
|
#include "openxrmanager.hpp"
|
||||||
|
|
||||||
|
struct XrSwapchainSubImage;
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
class OpenXRSwapchainImpl;
|
||||||
|
class VRFramebuffer;
|
||||||
|
|
||||||
|
/// \brief Creation and management of openxr swapchains
|
||||||
|
class OpenXRSwapchain
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OpenXRSwapchain(osg::ref_ptr<osg::State> state, SwapchainConfig config);
|
||||||
|
~OpenXRSwapchain();
|
||||||
|
|
||||||
|
public:
|
||||||
|
//! Prepare for render (set FBO)
|
||||||
|
void beginFrame(osg::GraphicsContext* gc);
|
||||||
|
|
||||||
|
//! Finalize render
|
||||||
|
void endFrame(osg::GraphicsContext* gc, VRFramebuffer& readBuffer);
|
||||||
|
|
||||||
|
//! Whether subchain is currently acquired (true) or released (false)
|
||||||
|
bool isAcquired() const;
|
||||||
|
|
||||||
|
//! Width of the view surface
|
||||||
|
int width() const;
|
||||||
|
|
||||||
|
//! Height of the view surface
|
||||||
|
int height() const;
|
||||||
|
|
||||||
|
//! Samples of the view surface
|
||||||
|
int samples() const;
|
||||||
|
|
||||||
|
//! Get the private implementation
|
||||||
|
OpenXRSwapchainImpl& impl() { return *mPrivate; }
|
||||||
|
|
||||||
|
//! Get the private implementation
|
||||||
|
const OpenXRSwapchainImpl& impl() const { return *mPrivate; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
OpenXRSwapchain(const OpenXRSwapchain&) = delete;
|
||||||
|
void operator=(const OpenXRSwapchain&) = delete;
|
||||||
|
private:
|
||||||
|
std::unique_ptr<OpenXRSwapchainImpl> mPrivate;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,221 @@
|
|||||||
|
#include "openxrswapchainimage.hpp"
|
||||||
|
#include "openxrmanagerimpl.hpp"
|
||||||
|
#include "vrenvironment.hpp"
|
||||||
|
#include "vrframebuffer.hpp"
|
||||||
|
|
||||||
|
// The OpenXR SDK's platform headers assume we've included platform headers
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <objbase.h>
|
||||||
|
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
#include <d3d11.h>
|
||||||
|
#include <dxgi1_2.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#elif __linux__
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <GL/glx.h>
|
||||||
|
#undef None
|
||||||
|
|
||||||
|
#else
|
||||||
|
#error Unsupported platform
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <openxr/openxr.h>
|
||||||
|
#include <openxr/openxr_platform.h>
|
||||||
|
#include <openxr/openxr_platform_defines.h>
|
||||||
|
#include <openxr/openxr_reflection.h>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#define GLERR if(auto err = glGetError() != GL_NO_ERROR) Log(Debug::Verbose) << __FILE__ << "." << __LINE__ << ": " << glGetError()
|
||||||
|
|
||||||
|
namespace MWVR {
|
||||||
|
|
||||||
|
template<typename Image>
|
||||||
|
class OpenXRSwapchainImageTemplate;
|
||||||
|
|
||||||
|
template<>
|
||||||
|
class OpenXRSwapchainImageTemplate< XrSwapchainImageOpenGLKHR > : public OpenXRSwapchainImage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr XrStructureType XrType = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR;
|
||||||
|
|
||||||
|
public:
|
||||||
|
OpenXRSwapchainImageTemplate(osg::GraphicsContext* gc, XrSwapchainCreateInfo swapchainCreateInfo, const XrSwapchainImageOpenGLKHR& xrImage)
|
||||||
|
: OpenXRSwapchainImage()
|
||||||
|
, mXrImage(xrImage)
|
||||||
|
, mBufferBits(0)
|
||||||
|
, mFramebuffer(nullptr)
|
||||||
|
{
|
||||||
|
mFramebuffer.reset(new VRFramebuffer(gc->getState(), swapchainCreateInfo.width, swapchainCreateInfo.height, swapchainCreateInfo.sampleCount));
|
||||||
|
if (swapchainCreateInfo.usageFlags & XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
|
||||||
|
{
|
||||||
|
mFramebuffer->setDepthBuffer(gc, mXrImage.image, false);
|
||||||
|
mBufferBits = GL_DEPTH_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mFramebuffer->setColorBuffer(gc, mXrImage.image, false);
|
||||||
|
mBufferBits = GL_COLOR_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void blit(osg::GraphicsContext* gc, VRFramebuffer& readBuffer, int offset_x, int offset_y) override
|
||||||
|
{
|
||||||
|
mFramebuffer->bindFramebuffer(gc, GL_FRAMEBUFFER_EXT);
|
||||||
|
readBuffer.blit(gc, offset_x, offset_y, offset_x + mFramebuffer->width(), offset_y + mFramebuffer->height(), 0, 0, mFramebuffer->width(), mFramebuffer->height(), mBufferBits, GL_NEAREST);
|
||||||
|
}
|
||||||
|
|
||||||
|
XrSwapchainImageOpenGLKHR mXrImage;
|
||||||
|
uint32_t mBufferBits;
|
||||||
|
std::unique_ptr<VRFramebuffer> mFramebuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
template<>
|
||||||
|
class OpenXRSwapchainImageTemplate< XrSwapchainImageD3D11KHR > : public OpenXRSwapchainImage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr XrStructureType XrType = XR_TYPE_SWAPCHAIN_IMAGE_D3D11_KHR;
|
||||||
|
public:
|
||||||
|
OpenXRSwapchainImageTemplate(osg::GraphicsContext* gc, XrSwapchainCreateInfo swapchainCreateInfo, const XrSwapchainImageD3D11KHR& xrImage)
|
||||||
|
: OpenXRSwapchainImage()
|
||||||
|
, mXrImage(xrImage)
|
||||||
|
, mBufferBits(0)
|
||||||
|
, mFramebuffer(nullptr)
|
||||||
|
{
|
||||||
|
mXrImage.texture->GetDevice(&mDevice);
|
||||||
|
mDevice->GetImmediateContext(&mDeviceContext);
|
||||||
|
|
||||||
|
mXrImage.texture->GetDesc(&mDesc);
|
||||||
|
|
||||||
|
glGenTextures(1, &mGlTextureName);
|
||||||
|
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
//mDxResourceShareHandle = xr->impl().platform().DXRegisterObject(mXrImage.texture, mGlTextureName, GL_TEXTURE_2D, true, nullptr);
|
||||||
|
|
||||||
|
if (!mDxResourceShareHandle)
|
||||||
|
{
|
||||||
|
// Some OpenXR runtimes return textures that cannot be directly shared.
|
||||||
|
// So we need to make a redundant texture to use as an intermediary...
|
||||||
|
mSharedTextureDesc.Width = mDesc.Width;
|
||||||
|
mSharedTextureDesc.Height = mDesc.Height;
|
||||||
|
mSharedTextureDesc.MipLevels = mDesc.MipLevels;
|
||||||
|
mSharedTextureDesc.ArraySize = mDesc.ArraySize;
|
||||||
|
mSharedTextureDesc.Format = static_cast<DXGI_FORMAT>(swapchainCreateInfo.format);
|
||||||
|
mSharedTextureDesc.SampleDesc = mDesc.SampleDesc;
|
||||||
|
mSharedTextureDesc.Usage = D3D11_USAGE_DEFAULT;
|
||||||
|
mSharedTextureDesc.BindFlags = 0;
|
||||||
|
mSharedTextureDesc.CPUAccessFlags = 0;
|
||||||
|
mSharedTextureDesc.MiscFlags = 0;;
|
||||||
|
|
||||||
|
mDevice->CreateTexture2D(&mSharedTextureDesc, nullptr, &mSharedTexture);
|
||||||
|
mXrImage.texture->GetDesc(&mSharedTextureDesc);
|
||||||
|
mDxResourceShareHandle = xr->impl().platform().DXRegisterObject(mSharedTexture, mGlTextureName, GL_TEXTURE_2D, true, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up shared texture as blit target
|
||||||
|
mFramebuffer.reset(new VRFramebuffer(gc->getState(), swapchainCreateInfo.width, swapchainCreateInfo.height, swapchainCreateInfo.sampleCount));
|
||||||
|
|
||||||
|
if (swapchainCreateInfo.usageFlags & XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
|
||||||
|
{
|
||||||
|
mFramebuffer->setDepthBuffer(gc, mGlTextureName, false);
|
||||||
|
mBufferBits = GL_DEPTH_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mFramebuffer->setColorBuffer(gc, mGlTextureName, false);
|
||||||
|
mBufferBits = GL_COLOR_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~OpenXRSwapchainImageTemplate()
|
||||||
|
{
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
if (mDxResourceShareHandle)
|
||||||
|
xr->impl().platform().DXUnregisterObject(mDxResourceShareHandle);
|
||||||
|
glDeleteTextures(1, &mGlTextureName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void blit(osg::GraphicsContext* gc, VRFramebuffer& readBuffer, int offset_x, int offset_y) override
|
||||||
|
{
|
||||||
|
// Blit readBuffer into directx texture, while flipping the Y axis.
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
xr->impl().platform().DXLockObject(mDxResourceShareHandle);
|
||||||
|
mFramebuffer->bindFramebuffer(gc, GL_FRAMEBUFFER_EXT);
|
||||||
|
readBuffer.blit(gc, offset_x, offset_y, offset_x + mFramebuffer->width(), offset_y + mFramebuffer->height(), 0, mFramebuffer->height(), mFramebuffer->width(), 0, mBufferBits, GL_NEAREST);
|
||||||
|
xr->impl().platform().DXUnlockObject(mDxResourceShareHandle);
|
||||||
|
|
||||||
|
|
||||||
|
// If the d3d11 texture couldn't be shared directly, blit it again.
|
||||||
|
if (mSharedTexture)
|
||||||
|
{
|
||||||
|
mDeviceContext->CopyResource(mXrImage.texture, mSharedTexture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ID3D11Device* mDevice = nullptr;
|
||||||
|
ID3D11DeviceContext* mDeviceContext = nullptr;
|
||||||
|
D3D11_TEXTURE2D_DESC mDesc;
|
||||||
|
D3D11_TEXTURE2D_DESC mSharedTextureDesc;
|
||||||
|
ID3D11Texture2D* mSharedTexture = nullptr;
|
||||||
|
uint32_t mGlTextureName = 0;
|
||||||
|
void* mDxResourceShareHandle = nullptr;
|
||||||
|
|
||||||
|
XrSwapchainImageD3D11KHR mXrImage;
|
||||||
|
uint32_t mBufferBits;
|
||||||
|
std::unique_ptr<VRFramebuffer> mFramebuffer;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
template< typename Image > static inline
|
||||||
|
std::vector<std::unique_ptr<OpenXRSwapchainImage> >
|
||||||
|
enumerateSwapchainImagesImpl(osg::GraphicsContext* gc, XrSwapchain swapchain, XrSwapchainCreateInfo swapchainCreateInfo)
|
||||||
|
{
|
||||||
|
using SwapchainImage = OpenXRSwapchainImageTemplate<Image>;
|
||||||
|
|
||||||
|
uint32_t imageCount = 0;
|
||||||
|
std::vector< Image > images;
|
||||||
|
CHECK_XRCMD(xrEnumerateSwapchainImages(swapchain, 0, &imageCount, nullptr));
|
||||||
|
images.resize(imageCount, { SwapchainImage::XrType });
|
||||||
|
CHECK_XRCMD(xrEnumerateSwapchainImages(swapchain, imageCount, &imageCount, reinterpret_cast<XrSwapchainImageBaseHeader*>(images.data())));
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<OpenXRSwapchainImage> > swapchainImages;
|
||||||
|
for(auto& image: images)
|
||||||
|
{
|
||||||
|
swapchainImages.emplace_back(new OpenXRSwapchainImageTemplate<Image>(gc, swapchainCreateInfo, image));
|
||||||
|
}
|
||||||
|
|
||||||
|
return swapchainImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<OpenXRSwapchainImage> >
|
||||||
|
OpenXRSwapchainImage::enumerateSwapchainImages(osg::GraphicsContext* gc, XrSwapchain swapchain, XrSwapchainCreateInfo swapchainCreateInfo)
|
||||||
|
{
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
|
||||||
|
if (xr->xrExtensionIsEnabled(XR_KHR_OPENGL_ENABLE_EXTENSION_NAME))
|
||||||
|
{
|
||||||
|
return enumerateSwapchainImagesImpl<XrSwapchainImageOpenGLKHR>(gc, swapchain, swapchainCreateInfo);
|
||||||
|
}
|
||||||
|
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||||
|
else if (xr->xrExtensionIsEnabled(XR_KHR_D3D11_ENABLE_EXTENSION_NAME))
|
||||||
|
{
|
||||||
|
return enumerateSwapchainImagesImpl<XrSwapchainImageD3D11KHR>(gc, swapchain, swapchainCreateInfo);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::logic_error("Implementation missing for selected graphics API");
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::vector<std::unique_ptr<OpenXRSwapchainImage>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenXRSwapchainImage::OpenXRSwapchainImage()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef OPENXR_SWAPCHAINIMAGE_HPP
|
||||||
|
#define OPENXR_SWAPCHAINIMAGE_HPP
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <openxr/openxr.h>
|
||||||
|
#include <osg/GraphicsContext>
|
||||||
|
|
||||||
|
#include "vrframebuffer.hpp"
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
class OpenXRSwapchainImage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static std::vector< std::unique_ptr<OpenXRSwapchainImage> >
|
||||||
|
enumerateSwapchainImages(osg::GraphicsContext* gc, XrSwapchain swapchain, XrSwapchainCreateInfo swapchainCreateInfo);
|
||||||
|
|
||||||
|
OpenXRSwapchainImage();
|
||||||
|
virtual ~OpenXRSwapchainImage() {};
|
||||||
|
|
||||||
|
virtual void blit(osg::GraphicsContext* gc, VRFramebuffer& readBuffer, int offset_x, int offset_y) = 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,263 @@
|
|||||||
|
#include "openxrswapchainimpl.hpp"
|
||||||
|
#include "openxrdebug.hpp"
|
||||||
|
#include "vrenvironment.hpp"
|
||||||
|
#include "vrframebuffer.hpp"
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
|
namespace MWVR {
|
||||||
|
OpenXRSwapchainImpl::OpenXRSwapchainImpl(osg::ref_ptr<osg::State> state, SwapchainConfig config)
|
||||||
|
: mConfig(config)
|
||||||
|
{
|
||||||
|
if (mConfig.selectedWidth <= 0)
|
||||||
|
throw std::invalid_argument("Width must be a positive integer");
|
||||||
|
if (mConfig.selectedHeight <= 0)
|
||||||
|
throw std::invalid_argument("Height must be a positive integer");
|
||||||
|
if (mConfig.selectedSamples <= 0)
|
||||||
|
throw std::invalid_argument("Samples must be a positive integer");
|
||||||
|
|
||||||
|
mSwapchain.reset(new SwapchainPrivate(state, mConfig, SwapchainPrivate::Use::COLOR));
|
||||||
|
mConfig.selectedSamples = mSwapchain->samples();
|
||||||
|
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
|
||||||
|
if (xr->xrExtensionIsEnabled(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mSwapchainDepth.reset(new SwapchainPrivate(state, mConfig, SwapchainPrivate::Use::DEPTH));
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
Log(Debug::Warning) << "XR_KHR_composition_layer_depth was enabled but creating depth swapchain failed: " << e.what();
|
||||||
|
mSwapchainDepth = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenXRSwapchainImpl::~OpenXRSwapchainImpl()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRSwapchainImpl::isAcquired() const
|
||||||
|
{
|
||||||
|
return mFormallyAcquired;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRSwapchainImpl::beginFrame(osg::GraphicsContext* gc)
|
||||||
|
{
|
||||||
|
acquire(gc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int swapCount = 0;
|
||||||
|
|
||||||
|
void OpenXRSwapchainImpl::endFrame(osg::GraphicsContext* gc, VRFramebuffer& readBuffer)
|
||||||
|
{
|
||||||
|
checkAcquired();
|
||||||
|
release(gc, readBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRSwapchainImpl::acquire(osg::GraphicsContext* gc)
|
||||||
|
{
|
||||||
|
if (isAcquired())
|
||||||
|
throw std::logic_error("Trying to acquire already acquired swapchain");
|
||||||
|
|
||||||
|
// The openxr runtime may fail to acquire/release.
|
||||||
|
// Do not re-acquire a swapchain before having successfully released it.
|
||||||
|
// Lest the swapchain fall out of sync.
|
||||||
|
if (!mShouldRelease)
|
||||||
|
{
|
||||||
|
mSwapchain->acquire(gc);
|
||||||
|
mShouldRelease = mSwapchain->isAcquired();
|
||||||
|
if (mSwapchainDepth && mSwapchain->isAcquired())
|
||||||
|
{
|
||||||
|
mSwapchainDepth->acquire(gc);
|
||||||
|
mShouldRelease = mSwapchainDepth->isAcquired();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mFormallyAcquired = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRSwapchainImpl::release(osg::GraphicsContext* gc, VRFramebuffer& readBuffer)
|
||||||
|
{
|
||||||
|
// The openxr runtime may fail to acquire/release.
|
||||||
|
// Do not release a swapchain before having successfully acquire it.
|
||||||
|
if (mShouldRelease)
|
||||||
|
{
|
||||||
|
mSwapchain->blitAndRelease(gc, readBuffer);
|
||||||
|
mShouldRelease = mSwapchain->isAcquired();
|
||||||
|
if (mSwapchainDepth)
|
||||||
|
{
|
||||||
|
mSwapchainDepth->blitAndRelease(gc, readBuffer);
|
||||||
|
mShouldRelease = mSwapchainDepth->isAcquired();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mFormallyAcquired = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRSwapchainImpl::checkAcquired() const
|
||||||
|
{
|
||||||
|
if (!isAcquired())
|
||||||
|
throw std::logic_error("Swapchain must be acquired before use. Call between OpenXRSwapchain::beginFrame() and OpenXRSwapchain::endFrame()");
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenXRSwapchainImpl::SwapchainPrivate::SwapchainPrivate(osg::ref_ptr<osg::State> state, SwapchainConfig config, Use use)
|
||||||
|
: mConfig(config)
|
||||||
|
, mImages()
|
||||||
|
, mWidth(config.selectedWidth)
|
||||||
|
, mHeight(config.selectedHeight)
|
||||||
|
, mSamples(1)
|
||||||
|
, mUsage(use)
|
||||||
|
{
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
|
||||||
|
XrSwapchainCreateInfo swapchainCreateInfo{ XR_TYPE_SWAPCHAIN_CREATE_INFO };
|
||||||
|
swapchainCreateInfo.arraySize = 1;
|
||||||
|
swapchainCreateInfo.width = mWidth;
|
||||||
|
swapchainCreateInfo.height = mHeight;
|
||||||
|
swapchainCreateInfo.mipCount = 1;
|
||||||
|
swapchainCreateInfo.faceCount = 1;
|
||||||
|
|
||||||
|
while (mSamples > 0 && mSwapchain == XR_NULL_HANDLE && mFormat == 0)
|
||||||
|
{
|
||||||
|
// Select a swapchain format.
|
||||||
|
if (use == Use::COLOR)
|
||||||
|
mFormat = xr->selectColorFormat();
|
||||||
|
else
|
||||||
|
mFormat = xr->selectDepthFormat();
|
||||||
|
std::string typeString = use == Use::COLOR ? "color" : "depth";
|
||||||
|
if (mFormat == 0) {
|
||||||
|
throw std::runtime_error(std::string("Swapchain ") + typeString + " format not supported");
|
||||||
|
}
|
||||||
|
Log(Debug::Verbose) << "Selected " << typeString << " format: " << std::dec << mFormat << " (" << std::hex << mFormat << ")" << std::dec;
|
||||||
|
|
||||||
|
// Now create the swapchain
|
||||||
|
Log(Debug::Verbose) << "Creating swapchain with dimensions Width=" << mWidth << " Heigh=" << mHeight << " SampleCount=" << mSamples;
|
||||||
|
swapchainCreateInfo.format = mFormat;
|
||||||
|
swapchainCreateInfo.sampleCount = mSamples;
|
||||||
|
if(mUsage == Use::COLOR)
|
||||||
|
swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
|
||||||
|
else
|
||||||
|
swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||||
|
auto res = xrCreateSwapchain(xr->impl().xrSession(), &swapchainCreateInfo, &mSwapchain);
|
||||||
|
|
||||||
|
// Check errors and try again if possible
|
||||||
|
if (res == XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED)
|
||||||
|
{
|
||||||
|
// We only try swapchain formats enumerated by the runtime itself.
|
||||||
|
// This does not guarantee that that swapchain format is going to be supported for this specific usage.
|
||||||
|
Log(Debug::Verbose) << "Failed to create swapchain with Format=" << mFormat<< ": " << XrResultString(res);
|
||||||
|
xr->eraseFormat(mFormat);
|
||||||
|
mFormat = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (!XR_SUCCEEDED(res))
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << "Failed to create swapchain with SampleCount=" << mSamples << ": " << XrResultString(res);
|
||||||
|
mSamples /= 2;
|
||||||
|
if (mSamples == 0)
|
||||||
|
{
|
||||||
|
CHECK_XRRESULT(res, "xrCreateSwapchain");
|
||||||
|
throw std::runtime_error(XrResultString(res));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_XRRESULT(res, "xrCreateSwapchain");
|
||||||
|
VrDebug::setName(mSwapchain, "OpenMW XR Color Swapchain " + config.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: here
|
||||||
|
mImages = OpenXRSwapchainImage::enumerateSwapchainImages(state->getGraphicsContext(), mSwapchain, swapchainCreateInfo);
|
||||||
|
mSubImage.swapchain = mSwapchain;
|
||||||
|
mSubImage.imageRect.offset = { 0, 0 };
|
||||||
|
mSubImage.imageRect.extent = { mWidth, mHeight };
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenXRSwapchainImpl::SwapchainPrivate::~SwapchainPrivate()
|
||||||
|
{
|
||||||
|
if (mSwapchain)
|
||||||
|
CHECK_XRCMD(xrDestroySwapchain(mSwapchain));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t OpenXRSwapchainImpl::SwapchainPrivate::count() const
|
||||||
|
{
|
||||||
|
return mImages.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenXRSwapchainImpl::SwapchainPrivate::isAcquired() const
|
||||||
|
{
|
||||||
|
return mIsReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRSwapchainImpl::SwapchainPrivate::acquire(osg::GraphicsContext* gc)
|
||||||
|
{
|
||||||
|
auto xr = Environment::get().getManager();
|
||||||
|
XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
|
||||||
|
XrSwapchainImageWaitInfo waitInfo{ XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO };
|
||||||
|
waitInfo.timeout = XR_INFINITE_DURATION;
|
||||||
|
if (!mIsIndexAcquired)
|
||||||
|
{
|
||||||
|
mIsIndexAcquired = XR_SUCCEEDED(CHECK_XRCMD(xrAcquireSwapchainImage(mSwapchain, &acquireInfo, &mAcquiredIndex)));
|
||||||
|
if (mIsIndexAcquired)
|
||||||
|
xr->xrResourceAcquired();
|
||||||
|
}
|
||||||
|
if (mIsIndexAcquired && !mIsReady)
|
||||||
|
{
|
||||||
|
mIsReady = XR_SUCCEEDED(CHECK_XRCMD(xrWaitSwapchainImage(mSwapchain, &waitInfo)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void OpenXRSwapchainImpl::SwapchainPrivate::blitAndRelease(osg::GraphicsContext* gc, VRFramebuffer& readBuffer)
|
||||||
|
{
|
||||||
|
auto xr = Environment::get().getManager();
|
||||||
|
|
||||||
|
XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
|
||||||
|
if (mIsReady)
|
||||||
|
{
|
||||||
|
mImages[mAcquiredIndex]->blit(gc, readBuffer, mConfig.offsetWidth, mConfig.offsetHeight);
|
||||||
|
|
||||||
|
mIsReady = !XR_SUCCEEDED(CHECK_XRCMD(xrReleaseSwapchainImage(mSwapchain, &releaseInfo)));
|
||||||
|
if (!mIsReady)
|
||||||
|
{
|
||||||
|
mIsIndexAcquired = false;
|
||||||
|
xr->xrResourceReleased();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRSwapchainImpl::SwapchainPrivate::checkAcquired() const
|
||||||
|
{
|
||||||
|
if (!isAcquired())
|
||||||
|
throw std::logic_error("Swapchain must be acquired before use. Call between OpenXRSwapchain::beginFrame() and OpenXRSwapchain::endFrame()");
|
||||||
|
}
|
||||||
|
|
||||||
|
XrSwapchain OpenXRSwapchainImpl::xrSwapchain(void) const
|
||||||
|
{
|
||||||
|
if(mSwapchain)
|
||||||
|
return mSwapchain->xrSwapchain();
|
||||||
|
return XR_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
XrSwapchain OpenXRSwapchainImpl::xrSwapchainDepth(void) const
|
||||||
|
{
|
||||||
|
if (mSwapchainDepth)
|
||||||
|
return mSwapchainDepth->xrSwapchain();
|
||||||
|
return XR_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
XrSwapchainSubImage OpenXRSwapchainImpl::xrSubImage(void) const
|
||||||
|
{
|
||||||
|
if (mSwapchain)
|
||||||
|
return mSwapchain->xrSubImage();
|
||||||
|
return XrSwapchainSubImage{ XR_NULL_HANDLE };
|
||||||
|
}
|
||||||
|
|
||||||
|
XrSwapchainSubImage OpenXRSwapchainImpl::xrSubImageDepth(void) const
|
||||||
|
{
|
||||||
|
if (mSwapchainDepth)
|
||||||
|
return mSwapchainDepth->xrSubImage();
|
||||||
|
return XrSwapchainSubImage{ XR_NULL_HANDLE };
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
#ifndef OPENXR_SWAPCHAINIMPL_HPP
|
||||||
|
#define OPENXR_SWAPCHAINIMPL_HPP
|
||||||
|
|
||||||
|
#include "openxrswapchainimage.hpp"
|
||||||
|
#include "openxrmanagerimpl.hpp"
|
||||||
|
|
||||||
|
struct XrSwapchainSubImage;
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
class VRFramebuffer;
|
||||||
|
|
||||||
|
/// \brief Implementation of OpenXRSwapchain
|
||||||
|
class OpenXRSwapchainImpl
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
struct SwapchainPrivate
|
||||||
|
{
|
||||||
|
enum class Use {
|
||||||
|
COLOR = 0,
|
||||||
|
DEPTH = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
SwapchainPrivate(osg::ref_ptr<osg::State> state, SwapchainConfig config, Use use);
|
||||||
|
~SwapchainPrivate();
|
||||||
|
|
||||||
|
uint32_t count() const;
|
||||||
|
bool isAcquired() const;
|
||||||
|
uint32_t acuiredIndex() const { return mAcquiredIndex; };
|
||||||
|
XrSwapchain xrSwapchain(void) const { return mSwapchain; };
|
||||||
|
XrSwapchainSubImage xrSubImage(void) const { return mSubImage; };
|
||||||
|
int width() const { return mWidth; };
|
||||||
|
int height() const { return mHeight; };
|
||||||
|
int samples() const { return mSamples; };
|
||||||
|
|
||||||
|
void acquire(osg::GraphicsContext* gc);
|
||||||
|
void blitAndRelease(osg::GraphicsContext* gc, VRFramebuffer& readBuffer);
|
||||||
|
void checkAcquired() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
private:
|
||||||
|
SwapchainConfig mConfig;
|
||||||
|
XrSwapchain mSwapchain = XR_NULL_HANDLE;
|
||||||
|
XrSwapchainSubImage mSubImage{};
|
||||||
|
std::vector< std::unique_ptr<OpenXRSwapchainImage> > mImages;
|
||||||
|
int32_t mWidth = -1;
|
||||||
|
int32_t mHeight = -1;
|
||||||
|
int32_t mSamples = -1;
|
||||||
|
int64_t mFormat = 0;
|
||||||
|
uint32_t mAcquiredIndex{ 0 };
|
||||||
|
Use mUsage;
|
||||||
|
bool mIsIndexAcquired{ false };
|
||||||
|
bool mIsReady{ false };
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
OpenXRSwapchainImpl(osg::ref_ptr<osg::State> state, SwapchainConfig config);
|
||||||
|
~OpenXRSwapchainImpl();
|
||||||
|
|
||||||
|
void beginFrame(osg::GraphicsContext* gc);
|
||||||
|
void endFrame(osg::GraphicsContext* gc, VRFramebuffer& readBuffer);
|
||||||
|
|
||||||
|
bool isAcquired() const;
|
||||||
|
XrSwapchain xrSwapchain(void) const;
|
||||||
|
XrSwapchain xrSwapchainDepth(void) const;
|
||||||
|
XrSwapchainSubImage xrSubImage(void) const;
|
||||||
|
XrSwapchainSubImage xrSubImageDepth(void) const;
|
||||||
|
int width() const { return mConfig.selectedWidth; };
|
||||||
|
int height() const { return mConfig.selectedHeight; };
|
||||||
|
int samples() const { return mConfig.selectedSamples; };
|
||||||
|
|
||||||
|
protected:
|
||||||
|
OpenXRSwapchainImpl(const OpenXRSwapchainImpl&) = delete;
|
||||||
|
void operator=(const OpenXRSwapchainImpl&) = delete;
|
||||||
|
|
||||||
|
void acquire(osg::GraphicsContext* gc);
|
||||||
|
void release(osg::GraphicsContext* gc, VRFramebuffer& readBuffer);
|
||||||
|
void checkAcquired() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SwapchainConfig mConfig;
|
||||||
|
std::unique_ptr<SwapchainPrivate> mSwapchain{ nullptr };
|
||||||
|
std::unique_ptr<SwapchainPrivate> mSwapchainDepth{ nullptr };
|
||||||
|
bool mFormallyAcquired{ false };
|
||||||
|
bool mShouldRelease{ false };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,141 @@
|
|||||||
|
#include "openxrinput.hpp"
|
||||||
|
#include "openxrmanagerimpl.hpp"
|
||||||
|
#include "openxrplatform.hpp"
|
||||||
|
#include "openxrtracker.hpp"
|
||||||
|
#include "openxrtypeconversions.hpp"
|
||||||
|
#include "vrenvironment.hpp"
|
||||||
|
#include "vrinputmanager.hpp"
|
||||||
|
#include "vrsession.hpp"
|
||||||
|
|
||||||
|
#include <components/misc/constants.hpp>
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
OpenXRTracker::OpenXRTracker(const std::string& name, XrSpace referenceSpace)
|
||||||
|
: VRTrackingSource(name)
|
||||||
|
, mReferenceSpace(referenceSpace)
|
||||||
|
, mTrackingSpaces()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenXRTracker::~OpenXRTracker()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRTracker::addTrackingSpace(VRPath path, XrSpace space)
|
||||||
|
{
|
||||||
|
mTrackingSpaces[path] = space;
|
||||||
|
notifyAvailablePosesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRTracker::deleteTrackingSpace(VRPath path)
|
||||||
|
{
|
||||||
|
mTrackingSpaces.erase(path);
|
||||||
|
notifyAvailablePosesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRTracker::setReferenceSpace(XrSpace referenceSpace)
|
||||||
|
{
|
||||||
|
mReferenceSpace = referenceSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VRPath> OpenXRTracker::listSupportedTrackingPosePaths() const
|
||||||
|
{
|
||||||
|
std::vector<VRPath> path;
|
||||||
|
for (auto& e : mTrackingSpaces)
|
||||||
|
path.push_back(e.first);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRTracker::updateTracking(DisplayTime predictedDisplayTime)
|
||||||
|
{
|
||||||
|
Environment::get().getInputManager()->xrInput().getActionSet(ActionSet::Tracking).updateControls();
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
auto* session = Environment::get().getSession();
|
||||||
|
|
||||||
|
auto& frame = session->getFrame(VRSession::FramePhase::Update);
|
||||||
|
frame->mViews[(int)ReferenceSpace::STAGE] = locateViews(predictedDisplayTime, xr->impl().getReferenceSpace(ReferenceSpace::STAGE));
|
||||||
|
frame->mViews[(int)ReferenceSpace::VIEW] = locateViews(predictedDisplayTime, xr->impl().getReferenceSpace(ReferenceSpace::VIEW));
|
||||||
|
}
|
||||||
|
|
||||||
|
XrSpace OpenXRTracker::getSpace(VRPath path)
|
||||||
|
{
|
||||||
|
auto it = mTrackingSpaces.find(path);
|
||||||
|
if (it != mTrackingSpaces.end())
|
||||||
|
return it->second;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
VRTrackingPose OpenXRTracker::getTrackingPoseImpl(DisplayTime predictedDisplayTime, VRPath path, VRPath reference)
|
||||||
|
{
|
||||||
|
VRTrackingPose pose;
|
||||||
|
pose.status = TrackingStatus::Good;
|
||||||
|
XrSpace space = getSpace(path);
|
||||||
|
XrSpace ref = reference == 0 ? mReferenceSpace : getSpace(reference);
|
||||||
|
if (space == 0 || ref == 0)
|
||||||
|
pose.status = TrackingStatus::NotTracked;
|
||||||
|
if (!!pose.status)
|
||||||
|
locate(pose, space, ref, predictedDisplayTime);
|
||||||
|
return pose;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenXRTracker::locate(VRTrackingPose& pose, XrSpace space, XrSpace reference, DisplayTime predictedDisplayTime)
|
||||||
|
{
|
||||||
|
XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION };
|
||||||
|
auto res = xrLocateSpace(space, mReferenceSpace, predictedDisplayTime, &location);
|
||||||
|
|
||||||
|
if (XR_FAILED(res))
|
||||||
|
{
|
||||||
|
// Call failed, exit.
|
||||||
|
CHECK_XRRESULT(res, "xrLocateSpace");
|
||||||
|
pose.status = TrackingStatus::RuntimeFailure;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that everything is being tracked
|
||||||
|
if (!(location.locationFlags & (XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT | XR_SPACE_LOCATION_POSITION_TRACKED_BIT)))
|
||||||
|
{
|
||||||
|
// It's not, data is stale
|
||||||
|
pose.status = TrackingStatus::Stale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that data is valid
|
||||||
|
if (!(location.locationFlags & (XR_SPACE_LOCATION_ORIENTATION_VALID_BIT | XR_SPACE_LOCATION_POSITION_VALID_BIT)))
|
||||||
|
{
|
||||||
|
// It's not, we've lost tracking
|
||||||
|
pose.status = TrackingStatus::Lost;
|
||||||
|
}
|
||||||
|
|
||||||
|
pose.pose = MWVR::Pose{
|
||||||
|
fromXR(location.pose.position),
|
||||||
|
fromXR(location.pose.orientation)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<View, 2> OpenXRTracker::locateViews(DisplayTime predictedDisplayTime, XrSpace reference)
|
||||||
|
{
|
||||||
|
std::array<XrView, 2> xrViews{ {{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 = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
|
||||||
|
viewLocateInfo.displayTime = predictedDisplayTime;
|
||||||
|
viewLocateInfo.space = reference;
|
||||||
|
|
||||||
|
auto* xr = Environment::get().getManager();
|
||||||
|
CHECK_XRCMD(xrLocateViews(xr->impl().xrSession(), &viewLocateInfo, &viewState, viewCount, &viewCount, xrViews.data()));
|
||||||
|
|
||||||
|
std::array<View, 2> vrViews{};
|
||||||
|
vrViews[(int)Side::LEFT_SIDE].pose = fromXR(xrViews[(int)Side::LEFT_SIDE].pose);
|
||||||
|
vrViews[(int)Side::RIGHT_SIDE].pose = fromXR(xrViews[(int)Side::RIGHT_SIDE].pose);
|
||||||
|
vrViews[(int)Side::LEFT_SIDE].fov = fromXR(xrViews[(int)Side::LEFT_SIDE].fov);
|
||||||
|
vrViews[(int)Side::RIGHT_SIDE].fov = fromXR(xrViews[(int)Side::RIGHT_SIDE].fov);
|
||||||
|
return vrViews;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenXRTrackingToWorldBinding::OpenXRTrackingToWorldBinding()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
#ifndef OPENXR_TRACKER_HPP
|
||||||
|
#define OPENXR_TRACKER_HPP
|
||||||
|
|
||||||
|
#include <openxr/openxr.h>
|
||||||
|
#include "vrtracking.hpp"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
//! Serves as a C++ wrapper of openxr spaces, but also bridges stage coordinates and game coordinates.
|
||||||
|
//! Supports the compulsory sets of paths.
|
||||||
|
class OpenXRTracker : public VRTrackingSource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OpenXRTracker(const std::string& name, XrSpace referenceSpace);
|
||||||
|
~OpenXRTracker();
|
||||||
|
|
||||||
|
void addTrackingSpace(VRPath path, XrSpace space);
|
||||||
|
void deleteTrackingSpace(VRPath path);
|
||||||
|
|
||||||
|
//! The base space used to reference everything else.
|
||||||
|
void setReferenceSpace(XrSpace referenceSpace);
|
||||||
|
|
||||||
|
std::vector<VRPath> listSupportedTrackingPosePaths() const override;
|
||||||
|
void updateTracking(DisplayTime predictedDisplayTime) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
VRTrackingPose getTrackingPoseImpl(DisplayTime predictedDisplayTime, VRPath path, VRPath reference = 0) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::array<View, 2> locateViews(DisplayTime predictedDisplayTime, XrSpace reference);
|
||||||
|
void locate(VRTrackingPose& pose, XrSpace space, XrSpace reference, DisplayTime predictedDisplayTime);
|
||||||
|
XrSpace getSpace(VRPath);
|
||||||
|
|
||||||
|
XrSpace mReferenceSpace;
|
||||||
|
std::map<VRPath, XrSpace> mTrackingSpaces;
|
||||||
|
};
|
||||||
|
|
||||||
|
//! Ties a tracked pose to the game world.
|
||||||
|
//! A movement tracking pose is selected by passing its path to the constructor.
|
||||||
|
//! All poses are transformed in the horizontal plane by moving the x,y origin to the position of the movement tracking pose, and then reoriented using the current orientation.
|
||||||
|
//! The movement tracking pose is effectively always at the x,y origin
|
||||||
|
//! The movement of the movement tracking pose is accumulated and can be read using the movement() call.
|
||||||
|
//! If this movement is ever consumed (such as by moving the character to follow the player) the consumed movement must be reported using consumeMovement().
|
||||||
|
class OpenXRTrackingToWorldBinding
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OpenXRTrackingToWorldBinding();
|
||||||
|
|
||||||
|
//! Re-orient the stage.
|
||||||
|
void setOrientation(float yaw, bool adjust);
|
||||||
|
osg::Quat getOrientation() const { return mOrientation; }
|
||||||
|
|
||||||
|
void setEyeLevel(float eyeLevel) { mEyeLevel = eyeLevel; }
|
||||||
|
float getEyeLevel() const { return mEyeLevel; }
|
||||||
|
|
||||||
|
void setSeatedPlay(bool seatedPlay) { mSeatedPlay = seatedPlay; }
|
||||||
|
bool getSeatedPlay() const { return mSeatedPlay; }
|
||||||
|
|
||||||
|
//! The player's movement within the VR stage. This accumulates until the movement has been consumed by calling consumeMovement()
|
||||||
|
osg::Vec3 movement() const;
|
||||||
|
|
||||||
|
//! Consume movement
|
||||||
|
void consumeMovement(const osg::Vec3& movement);
|
||||||
|
|
||||||
|
//! Recenter tracking by consuming all movement.
|
||||||
|
void recenter(bool resetZ);
|
||||||
|
|
||||||
|
void update(Pose movementTrackingPose);
|
||||||
|
|
||||||
|
//! Transforms a stage-referenced pose to be world-aligned.
|
||||||
|
//! \note Unlike VRTrackingSource::getTrackingPose() this does not take a reference path, as re-alignment is only needed when fetching a stage-referenced pose.
|
||||||
|
void alignPose(Pose& pose);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool mSeatedPlay = false;
|
||||||
|
bool mHasTrackingData = false;
|
||||||
|
float mEyeLevel = 0;
|
||||||
|
Pose mLastPose = Pose();
|
||||||
|
osg::Vec3 mMovement = osg::Vec3(0,0,0);
|
||||||
|
osg::Quat mOrientation = osg::Quat(0,0,0,1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,76 @@
|
|||||||
|
#include "openxrtypeconversions.hpp"
|
||||||
|
#include "openxrswapchain.hpp"
|
||||||
|
#include "openxrswapchainimpl.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
osg::Vec3 fromXR(XrVector3f v)
|
||||||
|
{
|
||||||
|
return osg::Vec3{ v.x, -v.z, v.y };
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::Quat fromXR(XrQuaternionf quat)
|
||||||
|
{
|
||||||
|
return osg::Quat{ quat.x, -quat.z, quat.y, quat.w };
|
||||||
|
}
|
||||||
|
|
||||||
|
XrVector3f toXR(osg::Vec3 v)
|
||||||
|
{
|
||||||
|
return XrVector3f{ v.x(), v.z(), -v.y() };
|
||||||
|
}
|
||||||
|
|
||||||
|
XrQuaternionf toXR(osg::Quat quat)
|
||||||
|
{
|
||||||
|
return XrQuaternionf{ static_cast<float>(quat.x()), static_cast<float>(quat.z()), static_cast<float>(-quat.y()), static_cast<float>(quat.w()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
MWVR::Pose fromXR(XrPosef pose)
|
||||||
|
{
|
||||||
|
return MWVR::Pose{ fromXR(pose.position), fromXR(pose.orientation) };
|
||||||
|
}
|
||||||
|
|
||||||
|
XrPosef toXR(MWVR::Pose pose)
|
||||||
|
{
|
||||||
|
return XrPosef{ toXR(pose.orientation), toXR(pose.position) };
|
||||||
|
}
|
||||||
|
|
||||||
|
MWVR::FieldOfView fromXR(XrFovf fov)
|
||||||
|
{
|
||||||
|
return MWVR::FieldOfView{ fov.angleLeft, fov.angleRight, fov.angleUp, fov.angleDown };
|
||||||
|
}
|
||||||
|
|
||||||
|
XrFovf toXR(MWVR::FieldOfView fov)
|
||||||
|
{
|
||||||
|
return XrFovf{ fov.angleLeft, fov.angleRight, fov.angleUp, fov.angleDown };
|
||||||
|
}
|
||||||
|
|
||||||
|
XrCompositionLayerProjectionView toXR(MWVR::CompositionLayerProjectionView layer)
|
||||||
|
{
|
||||||
|
XrCompositionLayerProjectionView xrLayer;
|
||||||
|
xrLayer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
|
||||||
|
xrLayer.subImage = toXR(layer.subImage, false);
|
||||||
|
xrLayer.pose = toXR(layer.pose);
|
||||||
|
xrLayer.fov = toXR(layer.fov);
|
||||||
|
xrLayer.next = nullptr;
|
||||||
|
|
||||||
|
return xrLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
XrSwapchainSubImage toXR(MWVR::SubImage subImage, bool depthImage)
|
||||||
|
{
|
||||||
|
XrSwapchainSubImage xrSubImage{};
|
||||||
|
if (depthImage)
|
||||||
|
xrSubImage.swapchain = subImage.swapchain->impl().xrSwapchainDepth();
|
||||||
|
else
|
||||||
|
xrSubImage.swapchain = subImage.swapchain->impl().xrSwapchain();
|
||||||
|
xrSubImage.imageRect.extent.width = subImage.width;
|
||||||
|
xrSubImage.imageRect.extent.height = subImage.height;
|
||||||
|
xrSubImage.imageRect.offset.x = subImage.x;
|
||||||
|
xrSubImage.imageRect.offset.y = subImage.y;
|
||||||
|
xrSubImage.imageArrayIndex = 0;
|
||||||
|
return xrSubImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
|||||||
|
#ifndef MWVR_OPENXRTYPECONVERSIONS_H
|
||||||
|
#define MWVR_OPENXRTYPECONVERSIONS_H
|
||||||
|
|
||||||
|
#include <openxr/openxr.h>
|
||||||
|
#include "vrtypes.hpp"
|
||||||
|
#include <osg/Vec3>
|
||||||
|
#include <osg/Quat>
|
||||||
|
|
||||||
|
namespace MWVR
|
||||||
|
{
|
||||||
|
/// Conversion methods between openxr types to osg/mwvr types. Includes managing the differing conventions.
|
||||||
|
Pose fromXR(XrPosef pose);
|
||||||
|
FieldOfView fromXR(XrFovf fov);
|
||||||
|
osg::Vec3 fromXR(XrVector3f);
|
||||||
|
osg::Quat fromXR(XrQuaternionf quat);
|
||||||
|
XrPosef toXR(Pose pose);
|
||||||
|
XrFovf toXR(FieldOfView fov);
|
||||||
|
XrVector3f toXR(osg::Vec3 v);
|
||||||
|
XrQuaternionf toXR(osg::Quat quat);
|
||||||
|
|
||||||
|
XrCompositionLayerProjectionView toXR(CompositionLayerProjectionView layer);
|
||||||
|
XrSwapchainSubImage toXR(SubImage, bool depthImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,347 @@
|
|||||||
|
#include "realisticcombat.hpp"
|
||||||
|
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/soundmanager.hpp"
|
||||||
|
|
||||||
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
namespace MWVR {
|
||||||
|
namespace RealisticCombat {
|
||||||
|
|
||||||
|
static const char* stateToString(SwingState florida)
|
||||||
|
{
|
||||||
|
switch (florida)
|
||||||
|
{
|
||||||
|
case SwingState_Cooldown:
|
||||||
|
return "Cooldown";
|
||||||
|
case SwingState_Impact:
|
||||||
|
return "Impact";
|
||||||
|
case SwingState_Ready:
|
||||||
|
return "Ready";
|
||||||
|
case SwingState_Swing:
|
||||||
|
return "Swing";
|
||||||
|
case SwingState_Launch:
|
||||||
|
return "Launch";
|
||||||
|
}
|
||||||
|
return "Error, invalid enum";
|
||||||
|
}
|
||||||
|
static const char* swingTypeToString(int type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ESM::Weapon::AT_Chop:
|
||||||
|
return "Chop";
|
||||||
|
case ESM::Weapon::AT_Slash:
|
||||||
|
return "Slash";
|
||||||
|
case ESM::Weapon::AT_Thrust:
|
||||||
|
return "Thrust";
|
||||||
|
case -1:
|
||||||
|
return "Fail";
|
||||||
|
default:
|
||||||
|
return "Invalid";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StateMachine::StateMachine(MWWorld::Ptr ptr, VRPath trackingPath)
|
||||||
|
: mPtr(ptr)
|
||||||
|
, mMinVelocity(Settings::Manager::getFloat("realistic combat minimum swing velocity", "VR"))
|
||||||
|
, mMaxVelocity(Settings::Manager::getFloat("realistic combat maximum swing velocity", "VR"))
|
||||||
|
, mTrackingPath(trackingPath)
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << "realistic combat minimum swing velocity: " << mMinVelocity;
|
||||||
|
Log(Debug::Verbose) << "realistic combat maximum swing velocity: " << mMaxVelocity;
|
||||||
|
Environment::get().getTrackingManager()->bind(this, "pcstage");
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::onTrackingUpdated(VRTrackingSource& source, DisplayTime predictedDisplayTime)
|
||||||
|
{
|
||||||
|
mTrackingInput = source.getTrackingPose(predictedDisplayTime, mTrackingPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StateMachine::canSwing()
|
||||||
|
{
|
||||||
|
if (mSwingType >= 0)
|
||||||
|
if (mVelocity >= mMinVelocity)
|
||||||
|
if (mSwingType != ESM::Weapon::AT_Thrust || mThrustVelocity >= 0.f)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions common to all transitions
|
||||||
|
void StateMachine::transition(
|
||||||
|
SwingState newState)
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << "Transition:" << stateToString(mState) << "->" << stateToString(newState);
|
||||||
|
mMaxSwingVelocity = 0.f;
|
||||||
|
mTimeSinceEnteredState = 0.f;
|
||||||
|
mMovementSinceEnteredState = 0.f;
|
||||||
|
mState = newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::reset()
|
||||||
|
{
|
||||||
|
mMaxSwingVelocity = 0.f;
|
||||||
|
mTimeSinceEnteredState = 0.f;
|
||||||
|
mVelocity = 0.f;
|
||||||
|
mPreviousPosition = osg::Vec3(0.f, 0.f, 0.f);
|
||||||
|
mState = SwingState_Ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isMeleeWeapon(int type)
|
||||||
|
{
|
||||||
|
if (MWMechanics::getWeaponType(type)->mWeaponClass != ESM::WeaponType::Melee)
|
||||||
|
return false;
|
||||||
|
if (type == ESM::Weapon::HandToHand)
|
||||||
|
return true;
|
||||||
|
if (type >= 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isSideSwingValidForWeapon(int type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ESM::Weapon::HandToHand:
|
||||||
|
case ESM::Weapon::BluntOneHand:
|
||||||
|
case ESM::Weapon::BluntTwoClose:
|
||||||
|
case ESM::Weapon::BluntTwoWide:
|
||||||
|
case ESM::Weapon::SpearTwoWide:
|
||||||
|
return true;
|
||||||
|
case ESM::Weapon::ShortBladeOneHand:
|
||||||
|
case ESM::Weapon::LongBladeOneHand:
|
||||||
|
case ESM::Weapon::LongBladeTwoHand:
|
||||||
|
case ESM::Weapon::AxeOneHand:
|
||||||
|
case ESM::Weapon::AxeTwoHand:
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::update(float dt, bool enabled)
|
||||||
|
{
|
||||||
|
auto* world = MWBase::Environment::get().getWorld();
|
||||||
|
auto& handPose = mTrackingInput.pose;
|
||||||
|
auto weaponType = world->getActiveWeaponType();
|
||||||
|
|
||||||
|
enabled = enabled && isMeleeWeapon(weaponType);
|
||||||
|
enabled = enabled && !!mTrackingInput.status;
|
||||||
|
|
||||||
|
if (mEnabled != enabled)
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
mEnabled = enabled;
|
||||||
|
}
|
||||||
|
if (!enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mTimeSinceEnteredState += dt;
|
||||||
|
|
||||||
|
|
||||||
|
// First determine direction of different swing types
|
||||||
|
|
||||||
|
// Discover orientation of weapon
|
||||||
|
osg::Quat weaponDir = handPose.orientation;
|
||||||
|
|
||||||
|
// Morrowind models do not hold weapons at a natural angle, so i rotate the hand forward
|
||||||
|
// to get a more natural angle on the weapon to allow more comfortable combat.
|
||||||
|
if (weaponType != ESM::Weapon::HandToHand)
|
||||||
|
weaponDir = osg::Quat(osg::PI_4, osg::Vec3{ 1,0,0 }) * weaponDir;
|
||||||
|
|
||||||
|
// Thrust means stabbing in the direction of the weapon
|
||||||
|
osg::Vec3 thrustDirection = weaponDir * osg::Vec3{ 0,1,0 };
|
||||||
|
|
||||||
|
// Slash and Chop are vertical, relative to the orientation of the weapon (direction of the sharp edge / hammer)
|
||||||
|
osg::Vec3 slashChopDirection = weaponDir * osg::Vec3{ 0,0,1 };
|
||||||
|
|
||||||
|
// Side direction of the weapon (i.e. The blunt side of the sword)
|
||||||
|
osg::Vec3 sideDirection = weaponDir * osg::Vec3{ 1,0,0 };
|
||||||
|
|
||||||
|
|
||||||
|
// Next determine current hand movement
|
||||||
|
|
||||||
|
// If tracking is lost, openxr will return a position of 0
|
||||||
|
// So i reset position when tracking is re-acquired to avoid a superspeed strike.
|
||||||
|
// Theoretically, the player's hand really could be at 0,0,0
|
||||||
|
// but that's a super rare case so whatever.
|
||||||
|
if (mPreviousPosition == osg::Vec3(0.f, 0.f, 0.f))
|
||||||
|
mPreviousPosition = handPose.position;
|
||||||
|
|
||||||
|
osg::Vec3 movement = handPose.position - mPreviousPosition;
|
||||||
|
mMovementSinceEnteredState += movement.length();
|
||||||
|
mPreviousPosition = handPose.position;
|
||||||
|
osg::Vec3 swingVector = movement / dt;
|
||||||
|
osg::Vec3 swingDirection = swingVector;
|
||||||
|
swingDirection.normalize();
|
||||||
|
|
||||||
|
// Compute swing velocities
|
||||||
|
|
||||||
|
// Thrust follows the orientation of the weapon. Negative thrust = no attack.
|
||||||
|
mThrustVelocity = swingVector * thrustDirection;
|
||||||
|
mVelocity = swingVector.length();
|
||||||
|
|
||||||
|
|
||||||
|
if (isSideSwingValidForWeapon(weaponType))
|
||||||
|
{
|
||||||
|
// Compute velocity in the plane normal to the thrust direction.
|
||||||
|
float thrustComponent = std::abs(mThrustVelocity / mVelocity);
|
||||||
|
float planeComponent = std::sqrt(1 - thrustComponent * thrustComponent);
|
||||||
|
mSlashChopVelocity = mVelocity * planeComponent;
|
||||||
|
mSideVelocity = -1000.f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If side swing is not valid for the weapon, count slash/chop only along in
|
||||||
|
// the direction of the weapon's edge.
|
||||||
|
mSlashChopVelocity = std::abs(swingVector * slashChopDirection);
|
||||||
|
mSideVelocity = std::abs(swingVector * sideDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float orientationVerticality = std::abs(thrustDirection * osg::Vec3{ 0,0,1 });
|
||||||
|
float swingVerticality = std::abs(swingDirection * osg::Vec3{ 0,0,1 });
|
||||||
|
|
||||||
|
// Pick swing type based on greatest current velocity
|
||||||
|
// Note i use abs() of thrust velocity to prevent accidentally triggering
|
||||||
|
// chop/slash when player is withdrawing the weapon.
|
||||||
|
if (mSideVelocity > std::abs(mThrustVelocity) && mSideVelocity > mSlashChopVelocity)
|
||||||
|
{
|
||||||
|
// Player is swinging with the "blunt" side of a weapon that
|
||||||
|
// cannot be used that way.
|
||||||
|
mSwingType = -1;
|
||||||
|
}
|
||||||
|
else if (std::abs(mThrustVelocity) > mSlashChopVelocity)
|
||||||
|
{
|
||||||
|
mSwingType = ESM::Weapon::AT_Thrust;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// First check if the weapon is pointing upwards. In which case slash is not
|
||||||
|
// applicable, and the attack must be a chop.
|
||||||
|
if (orientationVerticality > 0.707)
|
||||||
|
mSwingType = ESM::Weapon::AT_Chop;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Next check if the swing is more horizontal or vertical. A slash
|
||||||
|
// would be more horizontal.
|
||||||
|
if (swingVerticality > 0.707)
|
||||||
|
mSwingType = ESM::Weapon::AT_Chop;
|
||||||
|
else
|
||||||
|
mSwingType = ESM::Weapon::AT_Slash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (mState)
|
||||||
|
{
|
||||||
|
case SwingState_Cooldown:
|
||||||
|
return update_cooldownState();
|
||||||
|
case SwingState_Ready:
|
||||||
|
return update_readyState();
|
||||||
|
case SwingState_Swing:
|
||||||
|
return update_swingState();
|
||||||
|
case SwingState_Impact:
|
||||||
|
return update_impactState();
|
||||||
|
case SwingState_Launch:
|
||||||
|
return update_launchState();
|
||||||
|
default:
|
||||||
|
throw std::logic_error(std::string("You forgot to implement state ") + stateToString(mState) + " ya dingus");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::update_cooldownState()
|
||||||
|
{
|
||||||
|
if (mTimeSinceEnteredState >= mMinimumPeriod)
|
||||||
|
transition_cooldownToReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::transition_cooldownToReady()
|
||||||
|
{
|
||||||
|
transition(SwingState_Ready);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::update_readyState()
|
||||||
|
{
|
||||||
|
if (canSwing())
|
||||||
|
return transition_readyToLaunch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::transition_readyToLaunch()
|
||||||
|
{
|
||||||
|
transition(SwingState_Launch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::playSwish()
|
||||||
|
{
|
||||||
|
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
|
||||||
|
|
||||||
|
std::string sound = "Weapon Swish";
|
||||||
|
if (mStrength < 0.5f)
|
||||||
|
sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack
|
||||||
|
if (mStrength < 1.0f)
|
||||||
|
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack
|
||||||
|
else
|
||||||
|
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack
|
||||||
|
|
||||||
|
Log(Debug::Verbose) << "Swing: " << swingTypeToString(mSwingType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::update_launchState()
|
||||||
|
{
|
||||||
|
if (mMovementSinceEnteredState > mMinimumPeriod)
|
||||||
|
transition_launchToSwing();
|
||||||
|
if (!canSwing())
|
||||||
|
return transition_launchToReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::transition_launchToReady()
|
||||||
|
{
|
||||||
|
transition(SwingState_Ready);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::transition_launchToSwing()
|
||||||
|
{
|
||||||
|
playSwish();
|
||||||
|
transition(SwingState_Swing);
|
||||||
|
|
||||||
|
// As a special case, update the new state immediately to allow
|
||||||
|
// same-frame impacts.
|
||||||
|
update_swingState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::update_swingState()
|
||||||
|
{
|
||||||
|
mMaxSwingVelocity = std::max(mVelocity, mMaxSwingVelocity);
|
||||||
|
mStrength = std::min(1.f, (mMaxSwingVelocity - mMinVelocity) / mMaxVelocity);
|
||||||
|
|
||||||
|
// When velocity falls below minimum, transition to register the miss
|
||||||
|
if (!canSwing())
|
||||||
|
return transition_swingingToImpact();
|
||||||
|
// Call hit with simulated=true to check for hit without actually causing an impact
|
||||||
|
if (mPtr.getClass().hit(mPtr, mStrength, mSwingType, true))
|
||||||
|
return transition_swingingToImpact();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::transition_swingingToImpact()
|
||||||
|
{
|
||||||
|
mPtr.getClass().hit(mPtr, mStrength, mSwingType, false);
|
||||||
|
transition(SwingState_Impact);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::update_impactState()
|
||||||
|
{
|
||||||
|
if (mVelocity < mMinVelocity)
|
||||||
|
return transition_impactToCooldown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateMachine::transition_impactToCooldown()
|
||||||
|
{
|
||||||
|
transition(SwingState_Cooldown);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
#ifndef MWVR_REALISTICCOMBAT_H
|
||||||
|
#define MWVR_REALISTICCOMBAT_H
|
||||||
|
|
||||||
|
#include <components/esm/loadweap.hpp>
|
||||||
|
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
#include "../mwworld/ptr.hpp"
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
|
||||||
|
#include "vrenvironment.hpp"
|
||||||
|
#include "vrsession.hpp"
|
||||||
|
#include "vrtracking.hpp"
|
||||||
|
|
||||||
|
namespace MWVR {
|
||||||
|
namespace RealisticCombat {
|
||||||
|
|
||||||
|
/// Enum describing the current state of the MWVR::RealisticCombat::StateMachine
|
||||||
|
enum SwingState
|
||||||
|
{
|
||||||
|
SwingState_Ready,
|
||||||
|
SwingState_Launch,
|
||||||
|
SwingState_Swing,
|
||||||
|
SwingState_Impact,
|
||||||
|
SwingState_Cooldown,
|
||||||
|
};
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
/// \brief State machine for "realistic" combat in openmw vr
|
||||||
|
///
|
||||||
|
/// \sa SwingState
|
||||||
|
///
|
||||||
|
/// Initial state: Ready.
|
||||||
|
///
|
||||||
|
/// State Ready: Ready to initiate a new attack.
|
||||||
|
/// State Launch: Player has begun swinging his weapon.
|
||||||
|
/// State Swing: Currently swinging weapon.
|
||||||
|
/// State Impact: Contact made, weapon still swinging.
|
||||||
|
/// State Cooldown: Swing completed, wait a minimum period before next.
|
||||||
|
///
|
||||||
|
/// Transition rules:
|
||||||
|
/// Ready -> Launch: When the minimum velocity of swing is achieved.
|
||||||
|
/// Launch -> Ready: When the minimum velocity of swing is lost before minimum distance was swung.
|
||||||
|
/// Launch -> Swing: When minimum distance is swung.
|
||||||
|
/// - Play Swish sound
|
||||||
|
/// Swing -> Impact: When minimum velocity is lost, or when a hit is detected.
|
||||||
|
/// - Register hit based on max velocity observed in swing state
|
||||||
|
/// Impact -> Cooldown: When velocity returns below minimum.
|
||||||
|
/// Cooldown -> Ready: When the minimum period has passed since entering Cooldown state
|
||||||
|
///
|
||||||
|
///
|
||||||
|
struct StateMachine : public VRTrackingListener
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
StateMachine(MWWorld::Ptr ptr, VRPath trackingPath);
|
||||||
|
void update(float dt, bool enabled);
|
||||||
|
MWWorld::Ptr ptr() { return mPtr; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onTrackingUpdated(VRTrackingSource& source, DisplayTime predictedDisplayTime) override;
|
||||||
|
|
||||||
|
bool canSwing();
|
||||||
|
|
||||||
|
void playSwish();
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
void transition(SwingState newState);
|
||||||
|
|
||||||
|
void update_cooldownState();
|
||||||
|
void transition_cooldownToReady();
|
||||||
|
|
||||||
|
void update_readyState();
|
||||||
|
void transition_readyToLaunch();
|
||||||
|
|
||||||
|
void update_launchState();
|
||||||
|
void transition_launchToReady();
|
||||||
|
void transition_launchToSwing();
|
||||||
|
|
||||||
|
void update_swingState();
|
||||||
|
void transition_swingingToImpact();
|
||||||
|
|
||||||
|
void update_impactState();
|
||||||
|
void transition_impactToCooldown();
|
||||||
|
|
||||||
|
private:
|
||||||
|
MWWorld::Ptr mPtr;
|
||||||
|
const float mMinVelocity;
|
||||||
|
const float mMaxVelocity;
|
||||||
|
|
||||||
|
float mVelocity = 0.f;
|
||||||
|
float mMaxSwingVelocity = 0.f;
|
||||||
|
|
||||||
|
SwingState mState = SwingState_Ready;
|
||||||
|
int mSwingType = -1;
|
||||||
|
float mStrength = 0.f;
|
||||||
|
|
||||||
|
float mThrustVelocity{ 0.f };
|
||||||
|
float mSlashChopVelocity{ 0.f };
|
||||||
|
float mSideVelocity{ 0.f };
|
||||||
|
|
||||||
|
float mMinimumPeriod{ .25f };
|
||||||
|
|
||||||
|
float mTimeSinceEnteredState = { 0.f };
|
||||||
|
float mMovementSinceEnteredState = { 0.f };
|
||||||
|
|
||||||
|
bool mEnabled = false;
|
||||||
|
|
||||||
|
osg::Vec3 mPreviousPosition{ 0.f,0.f,0.f };
|
||||||
|
VRTrackingPose mTrackingInput = VRTrackingPose();
|
||||||
|
VRPath mTrackingPath = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue